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

@angelitolm/next-query

v0.2.1

Published

Revalidate tagged fetch() calls by tag in the Next.js App Router, with a dev panel that lists every cached fetch and query

Readme


pnpm add @angelitolm/next-query

Next.js 15 or 16, App Router, React 19.

Tag a fetch

Plain Next, no import:

// app/products/page.tsx
const res = await fetch(`${API}/products`, { next: { tags: ['products'], revalidate: 60 } })

Revalidate by tag

'use server'
import { revalidate } from '@angelitolm/next-query'

export async function renameProduct(id: string, name: string) {
  await db.rename(id, name)
  revalidate('products') // revalidateTag('products', { expire: 0 })
}

revalidate('products') expires exactly the tag products. revalidate(['products', id]) takes a key and expires its deepest tag, the same as revalidate('products/' + id). Call it from server actions and route handlers.

A page that exports the segment config export const revalidate = … can't also import revalidate: import it as import { revalidate as revalidateQuery } from '@angelitolm/next-query' there.

A hierarchy with tags()

import { tags } from '@angelitolm/next-query'

fetch(url, { next: { tags: tags(['products', id]) } }) // ['products', 'products/1']

Each product carries the tag products, so revalidate('products') also covers every product, and revalidate(['products', 1]) only product 1. A / inside a segment becomes %2F and a % becomes %25. A tag is at most 256 characters, and an entry has at most 128 tags.

Data that isn't fetch

import { query } from '@angelitolm/next-query'

const stats = await query(['stats'], () => db.stats(), { revalidate: 30 }) // tags: tags(['stats'])

query() caches an ORM or SDK call with unstable_cache under a key and tags it with tags(key), so revalidate and the panel treat it like a fetch. fn's result must be JSON-serializable: a Date comes back as a string. revalidate is seconds until the data is stale, default false (kept until you revalidate it).

The panel

// app/layout.tsx
import { NextQuery } from '@angelitolm/next-query'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <NextQuery />
      </body>
    </html>
  )
}

In next dev, a round launcher with the logo sits in the bottom-right corner, with a bubble showing the stale and error count. The panel lists the query() entries that have run and the tagged native fetches it reads from Next's fetch cache, each with a QUERY or FETCH badge, its status and a freshness bar that counts down to stale (revalidate: false shows "never stale"). Each card has a ↻ that revalidates its most specific (leaf) tags; click the products chip to revalidate every product. Open an entry for its details, its tags (click a tag chip to revalidate that tag) and its data or response as highlighted JSON with a copy button. "Revalidate all" revalidates every tag in the list (the filter doesn't narrow it), and the ↻ in the header only reloads the list. After a revalidate an entry reads "revalidated · refetches on next read" until the page reads it again. Cached fetches with no tags are counted in a hint at the bottom of the list. Use position="bottom-left" to move it.

Outside development <NextQuery /> renders nothing, and its server actions refuse to run. A self-hosted staging server can opt in for QA with a secret, as a deliberate exception; see Staging. A fetch or query shows up once it has run. Fetches inside 'use cache' are not listed.

The docs demo

NextQueryDemo renders the real panel on in-memory entries, for docs and demos: import { NextQueryDemo } from '@angelitolm/next-query/demo'. It lives in its own entry point, so an app that only mounts <NextQuery /> never bundles the panel.

How it works

revalidate(tag) calls revalidateTag(tag, { expire: 0 }). query() wraps unstable_cache and tags it with tags(key). The panel reads the fetch cache files in .next (in development, or on an opted-in staging server).

License

MIT