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

@bavecs/react-render-sentinel

v0.1.1

Published

React render observability that explains what changed, why it rendered, and whether you should care.

Readme

react-render-sentinel

React render observability that explains what changed, why it rendered, and whether you should care.

A development-time debugging library that watches component renders, figures out what changed, tries to identify why, scores whether it's worth caring about, and reports the results — without screaming "useMemo everything" at every render, because a re-render is not, by itself, a bug.

Render detected
      ↓
Was something actually changed?
      ↓
Is the render potentially avoidable?
      ↓
Did it cause meaningful work?
      ↓
Is it worth optimizing?
      ↓
Explain + prioritize

Packages

| Package | What it does | |---|---| | @react-render-sentinel/core | Framework-agnostic engine: snapshot comparison, reason classification, optimization scoring, cascade/aggregate reporting. No React dependency. | | @react-render-sentinel/react | React integration: <RenderSentinel> provider, withRenderSentinel HOC, useRenderSentinel hook. | | @react-render-sentinel/console-reporter | Formats render events, cascades, and aggregated reports for the console. | | @react-render-sentinel/devtools, @react-render-sentinel/cli | Reserved for future work — not implemented in v1 (see Non-goals). |

Quick start

npm i @bavecs/react-render-sentinel

The package is available on npm as @bavecs/react-render-sentinel.

Simplest possible setup — wrap your app root:

import { createRoot } from "react-dom/client";
import { RenderSentinel } from "@react-render-sentinel/react";

createRoot(document.getElementById("root")!).render(
  <RenderSentinel enabled={import.meta.env.DEV}>
    <App />
  </RenderSentinel>
);

Or instrument a single component without any provider at all (the lightest-weight entry point):

import { withRenderSentinel } from "@react-render-sentinel/react";

function UserTable(props: UserTableProps) {
  /* ... */
}

export default withRenderSentinel(UserTable, { name: "UserTable" });

Or use the hook form inside a component:

import { useRenderSentinel } from "@react-render-sentinel/react";

function UserTable(props: UserTableProps) {
  useRenderSentinel({ name: "UserTable", props, trackProps: true });
  return /* ... */;
}

What it looks like

⚠ Render Sentinel - UserList (#42)
Duration: 12.80ms

Why it rendered: Prop "filters" received a new reference, but its contents
are structurally equivalent to the previous render.

"filters" received a new reference:
Previous: { "status": "active", "role": "admin" }
Current:  { "status": "active", "role": "admin" }
Analysis: values are structurally equivalent.

Impact: Potentially avoidable
Priority: HIGH - 86/100

Render path: Dashboard -> UserList

Renders that aren't worth caring about get a single quiet line instead of a wall of red:

ℹ Icon - Prop reference changed, but render duration was negligible. (priority: LOW, no action recommended)

Configuration

<RenderSentinel
  enabled
  mode="smart"        // "all" | "smart" (default) | "strict"
  threshold={1}
  include={["UserList", "UserCard"]}
  exclude={["Icon"]}
  reporter="console"
  trackDuration
  detectCascades
  strictModeHandling="smart" // "smart" | "report" | "ignore"
>
  <App />
</RenderSentinel>

See RenderSentinelOptions for the full list, including comparison limits (maxDepth, maxKeys, maxArrayLength, timeoutMs) that bound structural comparison so the debugging tool can never cause a bigger performance problem than the one it's investigating.

Design principles this implementation follows

  • Prefer "unknown" over a misleading diagnostic. The classifier and function-reference comparator never claim certainty they don't have — see FunctionReferenceChange.certainty (capped at "medium") and the UNKNOWN / UNKNOWN_CHANGE categories.
  • False positives are worse than false negatives for a "should I care" tool. Negligible-duration renders are forced to LOW priority regardless of how unstable their props look (see analyzeRender's duration guard).
  • Never do more work than necessary to answer the question. Comparison escalates through reference → primitive → bounded structural equality, and bails out (reporting low confidence rather than lying) once it hits maxDepth / maxKeys / maxArrayLength / timeoutMs.
  • A component-type view and a component-instance view are different questions. 500 <UserCard> instances are reported as UserCard { instances: 500, avoidable: 420, ... }, not 500 separate log lines.

Non-goals (v1)

This is not: a React DevTools replacement, a full profiler replacement, a state management library, a React compiler, a linter, or an automatic code modifier. It observes and explains; it does not rewrite your code (there is no react-render-sentinel optimize and there never will be in v1).

Monorepo layout

react-render-sentinel/
├── packages/
│   ├── core/              framework-agnostic engine
│   ├── react/              React integration (provider, HOC, hook)
│   ├── console-reporter/   console output formatting
│   ├── devtools/           reserved, not yet implemented
│   └── cli/                reserved, not yet implemented
├── examples/
│   └── vite-react/         runnable example (Dashboard → UserList → UserCard)
└── docs/

Development

pnpm install
pnpm build        # turbo run build across all packages
pnpm typecheck     # turbo run typecheck across all packages
cd examples/vite-react && pnpm dev

What's implemented vs. spec

Everything in spec sections 1–21 has a working implementation: change detection engine (section 9), reason classifier (10), optimization scoring (11), console reporter (12), aggregated reporting (13), cascade detection (4, 14), the HOC/hook/provider APIs (15–17), the bounded snapshot/comparison model (18–19), StrictMode handling (20), and memo-awareness (21).

Not yet implemented (explicitly deferred in the spec too): context analysis (section 22, marked "v1.1 or v2" in the spec), the devtools and cli packages (scaffolded but empty — the console reporter is the v1 MVP output per spec section 12), and ref forwarding through withRenderSentinel.

One implementation note worth knowing: the scoring formula's frequency term is a genuine sliding 5-second window, so a component's score will keep climbing as long as it keeps rendering within that window and settle back down once the render rate drops — this is intentional (it's exactly the "did this happen a lot recently" signal the formula calls for), not a runaway counter.

Vécsei Balázs