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

@plenary/next

v0.5.1

Published

Plenary Next.js integration: webhook revalidation handlers and cache-tagged fetch.

Readme

@plenary/next

Next.js glue for Plenary: turn entry webhooks into cache revalidation, and tag content fetches so dependent pages revalidate themselves.

Webhook revalidation (App Router)

// app/api/revalidate/route.ts
import { createRevalidateHandler } from "@plenary/next";

export const POST = createRevalidateHandler({
  secret: process.env.PLENARY_WEBHOOK_SECRET!,
});

In the Plenary console, add a webhook on each model pointing at /api/revalidate, subscribed to the entry events, with the secret in an x-plenary-secret custom header (marked secret). That's the whole setup: every event revalidates the entry's own URL, built from its folder path and slug (/news + hello-world → /news/hello-world).

When a model's URLs don't mirror folder + slug, or other pages show its content, map it:

export const POST = createRevalidateHandler({
  secret: process.env.PLENARY_WEBHOOK_SECRET!,
  paths: {
    "blog-post": ({ entry }) => ["/blog", `/blog/${entry.slug}`],
    "global-settings": "/",           // fixed path or list also fine
  },
});

A mapped model replaces the default URL; compose with the exported entryUrl to keep it. Unmapped slugless entries (singletons) revalidate nothing. The events option narrows which entry events fire (default: all five — revalidation is idempotent, so over-firing beats a stale page). The console's Test button gets a dry run: a 200 reporting the paths it would have revalidated.

The handler answers 401/405/400 for requests that will never succeed, so bad deliveries fail fast in the delivery log instead of retrying for seven hours.

Cache tags — no path map at all

Path maps drift: add a page that renders a menu, forget to add it to the map, serve stale content. Tags don't. Give the content client a tagged fetch:

import { createClient } from "@plenary/client";
import { createTaggedFetch } from "@plenary/next";

export const plenary = createClient({
  baseUrl: process.env.PLENARY_URL!,
  siteKey: "main",
  apiKey: process.env.PLENARY_API_KEY!,
  locale: "en",
  fetch: createTaggedFetch(),
});

Every content read is now tagged plenary:model:<key> (and plenary:entry:<slugOrId> for single-entry reads). Enable tags on the handler:

export const POST = createRevalidateHandler({
  secret: process.env.PLENARY_WEBHOOK_SECRET!,
  tags: true,
});

Each event then also revalidates the model and entry tags — every page that fetched that content regenerates, including the ones you forgot you built.

Pages Router

// pages/api/revalidate.ts
import { createPagesRevalidateHandler } from "@plenary/next/pages";

export default createPagesRevalidateHandler({
  secret: process.env.PLENARY_WEBHOOK_SECRET!,
});

Same options, built on res.revalidate. Cache tags are an App Router feature, so tags is ignored here.

Authentication

Plenary does not sign webhook requests yet, so authentication is the shared secret above (constant-time compared; also accepted as a ?secret= query parameter, names configurable via secretHeader/secretQuery). Payload parsing and verification live in @plenary/client (parseWebhookRequest, verifyWebhookSecret, and the payload types) for use outside Next.js; HMAC verification will land there when signing ships.