react-perf-analyzer
A high-performance Rust CLI that detects React performance anti-patterns in JS/TS/JSX files using deep AST analysis.
⚡ Powered by OXC — the fastest JS/TS parser in the ecosystem.
Built with OXC (Rust-native JS/TS parser), Rayon for parallel file processing, and clap for the CLI.
Rules
| Rule | What it detects |
|---|---|
no_inline_jsx_fn |
Inline arrow/function expressions in JSX props — creates a new function reference on every render |
large_component |
React components exceeding a configurable logical-line threshold |
unstable_props |
Object/array literals in JSX props — creates a new reference on every render |
Installation
Prerequisites
- Rust (edition 2021, stable toolchain)
Build from source
The binary is at ./target/release/react-perf-analyzer.
Install globally
Then call react-perf-analyzer from anywhere.
Usage
react-perf-analyzer [OPTIONS] <PATH>
Arguments
| Argument | Description |
|---|---|
<PATH> |
File or directory to scan (recursively) |
Options
| Flag | Default | Description |
|---|---|---|
--format <text|json> |
text |
Output format |
--max-component-lines <N> |
300 |
Line threshold for large_component rule |
Examples
Scan a single file
Scan a full project
Scan an entire monorepo
node_modules, dist, and build directories are automatically skipped.
Tune the component size threshold
JSON output (for CI or tooling)
Global install, then scan from anywhere
Sample output
src/components/UserCard.tsx:14:17 warning unstable_props Object literal in 'style' prop creates a new reference on every render.
src/components/UserCard.tsx:21:24 warning no_inline_jsx_fn Inline arrow function in 'onClick' prop. Extract to a named handler or wrap with useCallback.
src/pages/Dashboard.tsx:1:1 warning large_component Component 'Dashboard' is 340 lines (310 logical) — limit is 300.
✖ 3 issues found
Scanned 42 file(s), found 3 issue(s).
Rule details
no_inline_jsx_fn
Detects inline functions passed as JSX props that create a new reference on every render, breaking React.memo and shouldComponentUpdate optimizations.
Detects:
- Arrow functions:
onClick={() => doSomething()} - Function expressions:
onClick={function() { ... }} - Functions inside ternaries:
onClick={flag ? () => a() : () => b()} - Functions inside logical expressions:
onClick={enabled && (() => submit())}
Ignores:
useCallback-wrapped functions:onClick={useCallback(() => ..., [deps])}useMemo-wrapped functions:render={useMemo(() => () => ..., [deps])}
Example fix:
// ❌ Before
// ✅ After
const handleDelete = ;
large_component
Flags React components whose logical line count (total lines minus blank lines and comments) exceeds the configured threshold. Counts logical lines to avoid penalizing well-commented code.
Reports:
- Total lines, logical lines, blank lines
- JSX element count
- Hook call count
- Context-aware suggestion (extract custom hook vs. split sub-components)
Detects all component forms:
function MyComponent() {}const MyComponent = () => {}export default function() {}export const MyComponent = () => {}memo()-wrapped componentsforwardRef()-wrapped components
Example fix:
// ❌ Before — one 400-line component doing everything
// ✅ After — split by concern
unstable_props
Detects object and array literals passed directly as JSX prop values. In JavaScript, {a:1} === {a:1} is false — a new reference is created on every render, defeating React.memo.
Detects:
- Direct object literals:
style={{ color: "red" }} - Direct array literals:
columns={["id", "name"]} - Literals inside ternaries:
options={flag ? { type: "bar" } : defaults} - Literals inside logical expressions:
config={enabled && { dense: true }} - Parenthesized literals:
style={({ color: "red" })}
Ignores:
useMemo-wrapped values:style={useMemo(() => ({ color }), [color])}React.useMemo-wrapped values- Stable variable references
Example fix:
// ❌ Before
// ✅ After — stable references
const COLUMNS = ;
Exit codes
| Code | Meaning |
|---|---|
0 |
No issues found |
1 |
One or more issues found |
Useful for CI pipelines:
||
Architecture
src/
├── main.rs # Entry point — Rayon parallel file pipeline
├── cli.rs # clap CLI definitions
├── file_loader.rs # Recursive file discovery (walkdir)
├── parser.rs # OXC JS/TS/JSX parser wrapper
├── analyzer.rs # Runs all rules against a parsed AST
├── reporter.rs # Text and JSON output formatters
├── utils.rs # Byte offset → line/column helper
└── rules/
├── mod.rs # Rule trait, Issue/Severity types
├── no_inline_jsx_fn.rs # Rule 1
├── large_component.rs # Rule 2
└── unstable_props.rs # Rule 3
Key dependencies:
| Crate | Purpose |
|---|---|
oxc_parser / oxc_ast / oxc_ast_visit |
Rust-native JS/TS/JSX parser and AST |
rayon |
Data-parallel file processing |
walkdir |
Recursive directory traversal |
clap |
CLI argument parsing |
serde / serde_json |
JSON output |
Test fixtures
# Rule 1 — inline functions (12 issues expected)
# Rule 2 — large components (5 issues expected)
# Rule 3 — unstable props (20 issues expected)
# All rules — combined bad component (15 issues expected)
# Zero issues — stable, well-written component