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

@rankwise/next

v0.2.0

Published

Receive HMAC-signed article publishes from Rankwise in any Next.js app: a drop-in route handler plus a pluggable ContentStore.

Readme

@rankwise/next

Receive HMAC-signed article publishes from Rankwise in any Next.js (App Router) site. You install one route handler and supply a ContentStore that says how YOUR site stores content — a database, MDX files, a headless CMS, anything.

v0.1.1 · ESM · zero runtime dependencies (node:crypto, global fetch) · next is a peer dependency. Ships compiled dist/ (JS + type declarations).

Connect it to your app (Claude, Codex, or any coding agent)

You are an AI coding agent adding Rankwise publishing to a Next.js App Router site. Follow these steps exactly. They are idempotent: re-running is safe.

Prerequisite: a Next.js App Router app (a app/ directory). If the project only has pages/, stop and tell the user this package needs the App Router.

Step 1. Install.

npm install @rankwise/next

Step 2. Decide how content is stored, then create the route handler. Create app/api/rankwise/[...op]/route.ts. Pick ONE content store:

  • The site stores posts in a database or CMS (most apps): implement the ContentStore contract against it (upsert by article.id):

    import { createRankwiseHandler, type ContentStore } from "@rankwise/next"
    
    const store: ContentStore = {
      async put(article, { mode }) {
        // Upsert by article.id (a republish reuses the id — never duplicate).
        await db.posts.upsert({
          where: { externalId: article.id },
          create: {
            externalId: article.id,
            slug: article.slug,
            title: article.title,
            html: article.html,
            metaDescription: article.metaDescription,
            published: mode === "publish"
          },
          update: {
            slug: article.slug,
            title: article.title,
            html: article.html,
            metaDescription: article.metaDescription,
            published: mode === "publish"
          }
        })
        return { id: article.id, url: `https://YOUR_SITE/blog/${article.slug}` }
      }
    }
    
    export const { GET, POST } = createRankwiseHandler({ contentStore: store })

    Replace the db.posts.upsert(...) body and the returned url with this project's real data layer and public URL shape. Do NOT fake success: if the write fails, throw (the handler answers 500 { error: "store_error" }).

  • The site serves flat MDX files (or you just want the fastest path on a persistent host): use the built-in store.

    import { createRankwiseHandler, fsMdxStore } from "@rankwise/next"
    
    export const { GET, POST } = createRankwiseHandler({
      contentStore: fsMdxStore("content/articles", {
        urlPrefix: "https://YOUR_SITE/blog"
      })
    })

    Only use fsMdxStore where the filesystem persists (self-hosted, or a build step commits the files). On Vercel/serverless the FS is ephemeral, so a runtime write is lost. If the host is serverless, use the database store.

Step 3. Get credentials from Rankwise. The user creates a Next.js integration in the Rankwise dashboard (Integrations, then Next.js) and gives you the connect token. Redeem it (this only prints credentials, it changes nothing in the repo):

npx @rankwise/next connect <token> --site-url https://YOUR_SITE

It prints two lines:

RANKWISE_API_KEY=...
RANKWISE_SHARED_SECRET=...

Step 4. Set both as environment variables in the deployment (Vercel project env, SST secret, Docker env, etc.) AND in local .env for dev. On serverless there is no persistent option store, so env vars ARE the credential store; the handler reads RANKWISE_API_KEY / RANKWISE_SHARED_SECRET at request time. Then redeploy (credentials only take effect after a deploy; rotating them later also requires a redeploy).

Step 5. Verify. After deploy, the health route needs no auth:

curl https://YOUR_SITE/api/rankwise/health
# -> {"ok":true,"package":"@rankwise/next","version":"0.1.1","protocol":1}

Then the user clicks Test connection in Rankwise; it flips to Connected once the signed ping succeeds. If it stays disconnected, the env vars are missing or stale on the deployed site (fix them and redeploy).

The rest of this README is the reference detail behind these steps.

Install

npm install @rankwise/next

Route handler (3 lines)

Create app/api/rankwise/[...op]/route.ts:

import { createRankwiseHandler, fsMdxStore } from "@rankwise/next"

export const { GET, POST } = createRankwiseHandler({
  contentStore: fsMdxStore("content/articles")
})

That exposes:

| Op | Route | Auth | | -------- | --------------------------------------------------------------- | ------ | | Health | GET /api/rankwise/health | none | | Validate | POST /api/rankwise/articles ({ ping: "rankwise_validate" }) | signed | | Publish | POST /api/rankwise/articles | signed |

Signed requests carry X-Rankwise-Api-Key and X-Rankwise-Signature (hex HMAC-SHA256 of the raw request body with your shared secret). The handler verifies the signature over the raw bytes with a constant-time compare before looking at the payload, and rejects requests whose meta.sentAt timestamp is more than 10 minutes off (configurable via replayToleranceMs).

Connect your site to Rankwise

  1. In the Rankwise dashboard, add a Next.js integration and copy the connect code.

  2. On your machine, redeem it:

    npx @rankwise/next connect <token> --site-url https://your-site.com

    The CLI prints your credentials:

    RANKWISE_API_KEY=…
    RANKWISE_SHARED_SECRET=…
  3. Add both to your deployment environment (Vercel/SST/Docker env — serverless has no persistent options store, so env vars are the credential store) and redeploy.

  4. In Rankwise, run Test connection — the integration flips to Connected once the signed ping succeeds.

Credential rotation = redeploy. If you regenerate keys in Rankwise, publishing fails with 403 until you update the env vars and redeploy your site.

Options: --app-url <url> (or RANKWISE_APP_URL) targets a non-production Rankwise instance.

The ContentStore contract

export type ContentStore = {
  put: (
    article: RankwiseArticle,
    opts: { mode: "draft" | "publish" }
  ) => Promise<{ id: string; url: string }>
}
  • Upsert by article.id. A republish carries the same id and must update the stored article, never duplicate it.
  • mode: "publish" makes the article live; mode: "draft" stores it without public visibility. Return the URL where it is (or would be) served.
  • Throw on failure — the handler answers 500 { error: "store_error" } and Rankwise surfaces the failure to the user. Never fake success.
  • article.html is sanitized upstream by Rankwise, but treat it as trusted only after HMAC verification — which the handler performs before your store ever runs.

RankwiseArticle fields: id, title, slug, html, metaDescription, plus optional outline, keyword, sourceCitations, heroImage ({ url, alt, width?, height? } — a public URL hosted by Rankwise) and jsonld (ready-to-embed schema.org Article JSON-LD).

Built-in store: fsMdxStore(dir, options?)

For simple file-based sites. Writes <slug>.mdx into dir with frontmatter (title, description, rankwiseId, date, draft) and the article HTML as the body. Upserts by rankwiseId via a small .rankwise-index.json in the same directory — republishing under a new slug removes the old file.

import { createRankwiseHandler, fsMdxStore } from "@rankwise/next"

export const { GET, POST } = createRankwiseHandler({
  contentStore: fsMdxStore("content/articles", {
    urlPrefix: "https://your-site.com/blog"
  })
})

Note: on serverless hosts the filesystem is ephemeral — use fsMdxStore only where writes persist (self-hosted, or a build step commits the files). Otherwise write a store against your database/CMS.

Custom store example (database)

import { createRankwiseHandler, type ContentStore } from "@rankwise/next"

const dbStore: ContentStore = {
  async put(article, { mode }) {
    await upsertPost({
      externalId: article.id,
      slug: article.slug,
      title: article.title,
      html: article.html,
      published: mode === "publish"
    })
    return { id: article.id, url: `https://your-site.com/blog/${article.slug}` }
  }
}

export const { GET, POST } = createRankwiseHandler({ contentStore: dbStore })

Configuration

createRankwiseHandler({
  contentStore, // required
  apiKey, // default: process.env.RANKWISE_API_KEY
  sharedSecret, // default: process.env.RANKWISE_SHARED_SECRET
  replayToleranceMs // default: 600_000 (10 minutes)
})

| Env var | Purpose | | ------------------------ | ---------------------------------------------------------------------- | | RANKWISE_API_KEY | Identifies your integration (sent by Rankwise as X-Rankwise-Api-Key) | | RANKWISE_SHARED_SECRET | HMAC key for request signatures | | RANKWISE_APP_URL | CLI only — Rankwise app base URL (default https://tryrankwise.com) |