React Server Components are great — until "use client" starts spreading through your codebase like a virus.
You added it to one file months ago. Now half your component tree is running on the client and you have no
idea which import dragged it there. Your bundle is massive, your Lighthouse score is suffering, and nobody
can explain why.
I built client-creep to solve this.
## What it does
npx client-creep
Zero setup. No install. Runs on any Next.js 13/14/15/16 project.
It answers three questions no existing tool answered together:
- Why is this a client component? — which import chain caused it
- Did it need to be? — or is it client purely by accident, with zero hooks or browser APIs?
- What is it costing you? — estimated KB being shipped to the browser
## Real output on a 534-file Next.js app
────────────────────────────────────────────────────────────
client-creep Next.js client component analysis
────────────────────────────────────────────────────────────
Files scanned: 534
Client components: 418 (182 boundaries)
Estimated client JS: 2.29 MB
Potentially recoverable: 237.4 KB (113 creep candidates)
────────────────────────────────────────────────────────────
⚠ Accidental Client Creep
────────────────────────────────────────────────────────────
⚠ src/app/chat-insights/components/EmptyStates.tsx 19.7 KB recoverable
No hooks, event handlers, or browser APIs detected
Why client:
⚡ src/app/chat-insights/page.tsx ← use client
└─ src/app/chat-insights/components/index.ts
└─ src/app/chat-insights/components/EmptyStates.tsx
That last section is the key one — accidental creep candidates. Components that are only client because
something upstream imported them through a barrel file. No hooks. No browser APIs. Just an accident.
## The full ecosystem
The CLI is the core, but there's more:
ESLint plugin — catches creep as you write code, not after:
npm install -D eslint-plugin-client-creep
// eslint.config.js
import clientCreep from 'eslint-plugin-client-creep'
export default [clientCreep.configs.recommended]
GitHub Action — posts a PR comment showing new creep introduced by a branch:
- uses: DhruvilChauahan0210/client-creep@main
with:
ci: true
budget: 500 # fail if > 500 KB client JS
Dashboard — track creep trend over time across PRs:
client-creep-dashboard.vercel.app
VS Code extension — inline diagnostics as you type (Marketplace publish coming soon).
## Interactive HTML graph
npx client-creep --html
Generates a D3 force-directed graph of your entire import graph, color-coded by component type. Click any
node to see its import chain and why it's client. Useful for showing the team where the real problems are.
## CI usage
# Fail if any accidental creep exists
npx client-creep --ci
# Fail if client JS exceeds 500 KB
npx client-creep --budget 500
## How it works
It's a static analyzer — no need to run your app.
- Globs all
.ts/.tsxfiles, skippingnode_modulesand.next - Parses each file with a Babel AST, detects
"use client"and extracts imports - Resolves imports including
tsconfigpath aliases and monorepo workspace packages - Builds a directed import graph and propagates client boundaries via BFS
- Flags nodes with no detected client signals (hooks, browser globals, event handlers) as accidental creep
Works in monorepos too — auto-detects pnpm/turbo/yarn workspaces and resolves cross-package imports.
GitHub: github.com/DhruvilChauahan0210/client-creep
npm: npx client-creep
Would love feedback — especially if you run it on your codebase and find something unexpected.
Top comments (0)