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

eslint-plugin-client-creep

v0.1.0

Published

ESLint plugin to catch accidental client components in Next.js App Router

Readme

eslint-plugin-client-creep

ESLint plugin to catch accidental client components in Next.js App Router.

Companion to client-creep — get the same insights inline in your editor and CI, one file at a time.


Rules

| Rule | What it catches | Auto-detects | |---|---|---| | no-unnecessary-use-client | "use client" files with no hooks, event handlers, or browser APIs | ✅ No setup needed | | no-client-creep | Files dragged into the client graph with no client signals | Needs client-creep installed or cache |


Install

npm install -D eslint-plugin-client-creep

Setup

ESLint 9 (flat config — eslint.config.js)

import clientCreep from "eslint-plugin-client-creep";

export default [
  clientCreep.configs.recommended,
];

ESLint 8 (legacy config — .eslintrc.js)

module.exports = {
  plugins: ["client-creep"],
  extends: ["plugin:client-creep/recommended-legacy"],
};

Manual rule config

// eslint.config.js
import clientCreep from "eslint-plugin-client-creep";

export default [
  {
    plugins: { "client-creep": clientCreep },
    rules: {
      "client-creep/no-unnecessary-use-client": "warn",
      "client-creep/no-client-creep": "warn",
    },
  },
];

Rule details

no-unnecessary-use-client

Flags "use client" directives in files that have no detectable client-only signals. These boundaries are likely accidents — they force the entire component subtree into the browser bundle for no reason.

Detected signals:

  • React hooks (useState, useEffect, any use[A-Z]*)
  • React.useXxx() namespaced calls
  • Event handler props (onClick, onChange, etc.)
  • Browser globals (window, document, localStorage, etc.)
  • Known client-only packages (framer-motion, @radix-ui/*, @apollo/client, etc.)
// ❌ flagged — no client signals
"use client";
export function Badge({ label }: { label: string }) {
  return <span>{label}</span>;
}

// ✅ fine — has a hook
"use client";
import { useState } from "react";
export function Counter() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n + 1)}>{n}</button>;
}

no-client-creep

Flags files that are in the client component graph but have no client-only signals — they're client purely because a parent imported them. These are candidates to be hoisted back to server components.

This rule needs the full import graph, which it gets by:

  1. Reading .client-creep-cache.json from the project root (fastest — pre-generate it)
  2. Running npx client-creep --json automatically if no cache is found

Pre-generate the cache (recommended for CI):

npx client-creep --json > .client-creep-cache.json

Add .client-creep-cache.json to .gitignore or commit it — your choice.

Or add to your package.json scripts:

{
  "scripts": {
    "lint": "npx client-creep --json > .client-creep-cache.json && eslint ."
  }
}

vs. running npx client-creep directly

| | npx client-creep | eslint-plugin-client-creep | |---|---|---| | Full project summary | ✅ | ❌ | | Import chain "why" trace | ✅ | ❌ | | Inline editor warnings | ❌ | ✅ | | CI lint failure | via --ci flag | via ESLint | | Per-file feedback | ❌ | ✅ | | Works with eslint --fix | ❌ | future |

Use both — client-creep for the full picture, the ESLint plugin for inline feedback as you write.


License

MIT