Detect unnecessary React rerenders in minutes, not hours.
@rayan_hn/render-inspector gives React and Next.js teams fast answers to three expensive questions:
- Why did this component rerender?
- Which subtree is rerendering too often?
- Which render/compute path is actually slow?
It is dev-first and production-safe by default (includeInProduction: false).
From npm (recommended):
npm install @rayan_hn/render-inspectorFrom GitHub Packages:
npm install @rayanhnide/render-inspector --registry=https://npm.pkg.github.comIf you use GitHub Packages, add this to your user .npmrc:
@rayanhnide:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}Then set GITHUB_TOKEN in your shell to a GitHub token with read:packages.
useRenderCount("CheckoutPanel", { warnAfter: 6 });
useWhyDidYouRender("CheckoutPanel", props, { deep: true });
useRenderTime("CheckoutPanel", { threshold: 12 });[CheckoutPanel] rerendered because 1 props changed
changed: onSubmit (function identity)
[CheckoutPanel] render commit took 18.24ms- CI runs typecheck, tests, and build on every PR and push to
main - Code scanning is enabled via CodeQL workflow
- Daily dependency checks are enabled with Dependabot
- OpenSSF Scorecard is available as an independent project health signal
- Semantic releases run on every
mainpush to auto-version, tag, publish to npm, and create GitHub releases
- Understand why components rerender and which props changed
- Track rerender counts and highlight noisy components
- Measure render commit time and expensive memoized tasks
- Keep function identity stable with
useStableCallback - Share config app-wide with
PerformanceProvider
- Next.js dashboard input lag:
docs/recipes/nextjs-dashboard-input-lag.md - Product list rerender storms:
docs/recipes/product-list-rerender-storm.md - Expensive memoized computations:
docs/recipes/expensive-memo-debugging.md
- Profiler is great for deep session analysis
- Render Inspector is better for always-on in-code signals while building features
why-did-you-renderfocuses on rerender reasons- Render Inspector additionally tracks render counts, render commit timings, and expensive memoized tasks in one package
"use client";
import { useRenderCount, useRenderTime, useWhyDidYouRender } from "@rayan_hn/render-inspector";
export function ProductCard(props: { name: string; price: number; filter: string }) {
useRenderCount("ProductCard", { warnAfter: 8 });
useWhyDidYouRender("ProductCard", props, { deep: true, ignoreKeys: ["filter"] });
useRenderTime("ProductCard", { threshold: 10 });
return <div>{props.name} - ${props.price}</div>;
}Example console output:
[ProductCard] rendered 6 times
[ProductCard] rerendered because 2 props changed
[ProductCard] render commit took 19.52ms- React 18+
- Next.js (App Router + Client Components)
- Vite React apps
- TypeScript and JavaScript
useWhyDidYouRender("UserCard", props, {
deep: true,
ignoreKeys: ["updatedAt", "lastSeenAt"]
});import { RenderGuard } from "@rayan_hn/render-inspector";
<RenderGuard name="Sidebar" limit={6}>
<Sidebar />
</RenderGuard>;const visibleProducts = useExpensiveTask(
"filter-products",
() => products.filter(matchesFilters),
[products, matchesFilters],
{ threshold: 8 }
);Use PerformanceProvider in app/layout.tsx to share defaults:
import type { ReactNode } from "react";
import { PerformanceProvider } from "@rayan_hn/render-inspector";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<PerformanceProvider slowRenderThreshold={12}>{children}</PerformanceProvider>
</body>
</html>
);
}Returns the current render count and logs each render based on options.
const count = useRenderCount("DashboardTable", {
warnAfter: 10,
logEvery: 1
});Diffs previous and current props and logs what changed.
useWhyDidYouRender("UserCard", props, {
deep: true,
ignoreKeys: ["updatedAt"],
logOnMount: false
});Measures render-to-commit duration and warns when threshold is exceeded.
useRenderTime("AnalyticsPanel", {
threshold: 16,
logEveryRender: false
});Simple wrapper around useRenderCount for subtree guardrails.
<RenderGuard name="CheckoutPanel" limit={5}>
<CheckoutPanel />
</RenderGuard>Keeps callback identity stable while always invoking the latest callback body.
Memoizes value when dependencies are deeply equal (useful for object-shaped deps).
Measures expensive computed work inside useMemo and warns over threshold.
Manually time custom event-handler workflows.
const mark = usePerformanceMark("Checkout");
function onCheckout() {
mark("calculate totals", () => {
calculateTotals(cart);
});
}The library is dev-first:
includeInProductiondefaults tofalse- hooks are effectively no-op in production unless explicitly enabled
- provider-level config controls behavior globally
<PerformanceProvider
enabled={process.env.NODE_ENV === "development"}
includeInProduction={false}
slowRenderThreshold={16}
>
<App />
</PerformanceProvider>No by default. Logging and checks are disabled in production unless you opt in with includeInProduction.
No. It complements Profiler by giving in-code, component-level signals while you build.
Yes. Add the provider in layout and use hooks in client components.
See examples/next-app for ready-to-use snippets.
To help increase adoption, this repo includes:
docs/GROWTH_PLAN.md- a 30-day distribution plandocs/SOCIAL_POSTS.md- ready-to-publish social contentdocs/RELEASE_TEMPLATE.md- weekly release checklist and changelog template
For contributors and maintainers:
CONTRIBUTING.md- setup and contribution processCODE_OF_CONDUCT.md- community behavior expectationsSECURITY.md- private vulnerability reportingROADMAP.md- upcoming prioritiesCHANGELOG.md- release history and highlights
MIT © 2026 Rayan Hnide