A lightweight, zero-dependency React and Next.js library to visually debug and analyze component re-renders.
With react-rerender-debugger, you can wrap any component and instantly understand how often it re-renders and exactly what changed to cause the render.
- Visual Flashing Indicator: Components flash whenever they re-render.
- Smart DOM Tracking: Differentiate between harmless Virtual DOM re-renders and costly Real DOM mutations. Wait for actual mutations to flash, avoiding the "Memo trap" (over-engineering with useMemo).
- Render Analytics: Displays real-time render counts and exact causes (
Changed: props.user.id). - Zero Production Overhead: Fully isolated. In production mode, the library is a complete No-Op and leaves your DOM completely untouched.
- Smart Value Comparison: Deep/Shallow props inspection. Can ignore inline function changes gracefully.
- Next.js App Router Support: Seamless integration as a Client Component across all modern React frameworks.
- Lightweight & Zero-Dependency: Ships only exactly what is needed for local development.
npm install react-rerender-debugger
# or
yarn add react-rerender-debugger
# or
pnpm add react-rerender-debuggerYou can use the debugger in two ways: as a Higher-Order Component (HOC) or as a Wrapper Component.
Wrap any component and pass the props you want to monitor.
import { RenderTrace } from 'react-rerender-debugger';
import { UserCard } from './UserCard';
export default function App({ user, theme }) {
return (
<RenderTrace name="UserCard Wrapper" config={{ color: 'blue' }} track={{ theme }}>
<UserCard user={user} />
</RenderTrace>
);
}Wrap your component definition directly. It will automatically listen to all props passed into the component.
import React, { useState } from 'react';
import { withRenderTrace } from 'react-rerender-debugger';
const MyButton = ({ label, onClick }) => {
return <button onClick={onClick}>{label}</button>;
};
// Automatically inspects `label` and `onClick` changes
export default withRenderTrace(MyButton, { color: '#ff0000', duration: 300 });Both <RenderTrace> and withRenderTrace accept a config object:
| Prop | Type | Default | Description |
|---|---|---|---|
color |
string |
'#ff0000' |
The color of the flashing border and the badge text. |
duration |
number |
300 |
How long the border flashes in milliseconds. |
depth |
number |
1 |
Depth of object comparison. 1 means shallow comparison. 2 means it will check one level inside props to report props.user.name changed. |
flashOnDOMUpdateOnly |
boolean |
false |
If true, the UI will only flash if the Real DOM actually mutates. Great for avoiding unnecessary useMemo optimizations! |
hotRenderThresholdMs |
number |
16 |
If flashOnDOMUpdateOnly is enabled, and the V-DOM evaluation takes longer than this threshold, it overrides the DOM check and fires a Hot Render Warning (orange flash). |
vdomRenderIndicator |
'dot' | 'badge' | 'none' |
'dot' |
When flashOnDOMUpdateOnly is enabled, decides what to show for fast V-DOM only renders. |
as |
string |
'div' |
The HTML tag used as the wrapper. Adjust this (e.g. 'span' or 'tr') to prevent CSS flexbox/grid breakages. |
wrapperClassName |
string |
undefined |
Passthrough CSS class directly applied to the wrapper element for advanced layout control. |
wrapperStyle |
object |
undefined |
Passthrough inline styles directly applied to the wrapper element for advanced layout control. |
ignoreFunctions |
boolean |
false |
If true, ignores inline function references (onClick={() => set()}) from causing false positive alerts. |
ignoreReactNodes |
boolean |
true |
If true, gracefully ignores false positive noises caused by React elements passed via props (e.g. children), since new V-DOM elements change reference endlessly. |
Because React does not expose internal component state or Context changes to a parent wrapper, the debugger will log "State/Context change" if the component re-renders but no props have changed.
If you want to track specific states, you can explicitly pass them via the track prop on the wrapper. You can track as many variables as you want by passing them as an object:
<RenderTrace track={{ myLocalState, someOtherState, myContextData }}>
<Component />
</RenderTrace>We designed react-rerender-debugger to be left inside your codebase without fear.
The library uses constant-folding logic:
export const RenderTrace = process.env.NODE_ENV === 'production'
? ({ children }) => <>{children}</>
: RenderTraceDev;When building your app for production (Next.js, Vite, Webpack, Rollup), the bundler recognizes process.env.NODE_ENV === 'production' is true, and completely strips out all debugging logic. The compiled wrapper becomes a literal React Fragment, adding absolutely zero processing overhead, zero DOM wrappers, and zero bundle size to your production applications.
While react-rerender-debugger covers the vast majority of debugging needs with zero production overhead, the following edge cases exist due to constraints in React and the DOM Observer API:
-
React Portals (
createPortal): Content rendered into external portals (e.g.document.body) is not observable byMutationObserverwhich is scoped to the wrapper element. In Smart DOM mode, these mutations will appear as silent V-DOM-only renders even if real DOM was updated. -
React 18 Concurrent Features (
useTransition,useDeferredValue, etc.): React may evaluate a component's render function multiple times before a single commit phase in concurrent mode. The Render Duration (ms) value reflects only the final synchronous pass, not the sum of all interrupted evaluations. -
useRefMutations: Mutating aref.currentdoes not trigger React's prop/state change detection. If a re-render is caused purely by a ref mutation, the tool will display"State/Context change"generically. Use thetrackprop to explicitly surface ref values you care about:track={{ myRef: myRef.current }}. -
JS-only Render Timing: The
msvalue covers JavaScript execution time only (the component function body). It does not account for subsequent browser work such as CSS recalculation, layout, paint, or compositor steps. A component with a 1ms JS render can still cause expensive layout reflows if it triggers forced layout reads/writes. -
Context Identity: When a component re-renders due to a Context value change, the tool correctly logs
"State/Context change"— but it cannot identify which Context triggered the update. This is a React API limitation; context subscriptions are internal to React and not observable from a wrapper component.
MIT