visualiser-performance

React Flow performance rules and review checklist for the @eventcatalog/visualiser package. Automatically applies when making changes to any file under packages/visualiser/. Use this skill to audit, review, or implement visualiser code with performance in mind.

16 stars

Best use case

visualiser-performance is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

React Flow performance rules and review checklist for the @eventcatalog/visualiser package. Automatically applies when making changes to any file under packages/visualiser/. Use this skill to audit, review, or implement visualiser code with performance in mind.

Teams using visualiser-performance should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/visualiser-performance/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/backend/visualiser-performance/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/visualiser-performance/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How visualiser-performance Compares

Feature / Agentvisualiser-performanceStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

React Flow performance rules and review checklist for the @eventcatalog/visualiser package. Automatically applies when making changes to any file under packages/visualiser/. Use this skill to audit, review, or implement visualiser code with performance in mind.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Visualiser Performance Rules

When modifying any code in `packages/visualiser/`, follow these rules to avoid React Flow performance regressions. A single unoptimized line can cause all nodes to re-render on every drag tick, dropping FPS from 60 to 2.

## Rule 1: Never pass unstable references to `<ReactFlow>` props

All props on `<ReactFlow>` must be referentially stable:

- **Objects/arrays**: Define outside the component or wrap in `useMemo` with stable deps
- **Functions**: Wrap in `useCallback` with stable deps
- **NEVER** pass anonymous functions (`onClick={() => {}}`) or inline objects directly

```tsx
// BAD - anonymous function causes ALL nodes to re-render on every state change
<ReactFlow onNodeClick={() => {}} />

// GOOD
const handleNodeClick = useCallback(() => {}, []);
<ReactFlow onNodeClick={handleNodeClick} />
```

`nodeTypes` and `edgeTypes` must be memoized with `useMemo(() => ..., [])` or defined outside the component. These are currently correct in `NodeGraph.tsx`.

## Rule 2: Never depend on `nodes`/`edges` arrays for structural data

The `nodes` and `edges` arrays from `useNodesState`/`useEdgesState` get new references on every position change (drag). If you put them in a `useMemo`/`useEffect` dependency array, that code runs on every drag tick.

**Pattern: Use stable structural keys**

When you only care about which nodes exist (not their positions), derive a stable key using `useRef`:

```tsx
// Stable key - only changes when nodes are added/removed
const nodeIdsKeyRef = useRef("");
const computedKey = nodes.map((n) => n.id).join(",");
if (computedKey !== nodeIdsKeyRef.current) {
  nodeIdsKeyRef.current = computedKey;
}
const nodeIdsKey = nodeIdsKeyRef.current;

// Now use nodeIdsKey instead of nodes in deps
const searchNodes = useMemo(() => nodes, [nodeIdsKey]);
```

For edges, include source/target in the key:
```tsx
const edgeKey = edges.map((e) => `${e.source}-${e.target}`).join(",");
```

**Never do this:**
```tsx
// BAD - runs on every drag tick
useEffect(() => { /* expensive work */ }, [nodes, edges]);

// BAD - filter runs on every position change
const selected = useMemo(() => nodes.filter(n => n.selected), [nodes]);
```

## Rule 3: Always wrap custom nodes and edges in `memo()`

Every custom node and edge component MUST be wrapped in `React.memo`. This is the single most impactful optimization — it prevents node content from re-rendering during drag even if parent state changes.

```tsx
// GOOD
export default memo(function MyNode(props: NodeProps) {
  return <div>...</div>;
});
```

All current node components (`ServiceNode`, `EventNode`, `CommandNode`, `QueryNode`, `ChannelNode`, `DataNode`, `ViewNode`, `ActorNode`, `NoteNode`, `ExternalSystem`, `Custom`, `Entity`, `Step`, `Domain`, `Flow`, `DataProduct`, `User`) are correctly wrapped.

All edge components (`AnimatedMessageEdge`, `MultilineEdgeLabel`, `FlowEdge`) are correctly wrapped.

**Do not break this pattern when adding new node or edge types.**

## Rule 4: Memoize heavy sub-components inside nodes

If a node renders complex sub-components (data grids, forms, SVG animations), wrap those in `memo()` too. This prevents the inner content from re-rendering even when the node itself re-renders.

```tsx
// Sub-components with static or rarely-changing props should be memoized
const GlowHandle = memo(function GlowHandle({ side }: { side: "left" | "right" }) {
  return <div style={{...}} />;
});
```

Currently memoized sub-components: `GlowHandle` (in ServiceNode, EventNode, CommandNode, QueryNode), `MiniEnvelope`, `ServiceMessageFlow` (in ServiceNode).

## Rule 5: Avoid `useStore` selectors that return new references

If using ReactFlow's `useStore` (or any Zustand store), never return arrays/objects that get recreated on every state change:

```tsx
// BAD - new array reference on every state update
const selected = useStore(state => state.nodes.filter(n => n.selected));

// GOOD - extract primitive values, or use useShallow
const selectedIds = useStore(
  state => state.nodes.filter(n => n.selected).map(n => n.id)
);
// With useShallow for object/array returns
import { useShallow } from 'zustand/react/shallow';
const [a, b] = useStore(useShallow(state => [state.a, state.b]));
```

## Checklist for PR review

When reviewing visualiser changes, verify:

- [ ] No anonymous functions or inline objects passed to `<ReactFlow>` props
- [ ] No `useMemo`/`useEffect`/`useCallback` with `nodes` or `edges` in deps (use structural keys instead)
- [ ] New custom nodes/edges are wrapped in `memo()`
- [ ] Heavy sub-components inside nodes are wrapped in `memo()`
- [ ] No `useStore` selectors returning unstable references
- [ ] `nodeTypes`/`edgeTypes` remain memoized with empty deps

## Key files

| File | What to check |
|------|--------------|
| `src/components/NodeGraph.tsx` | ReactFlow props, structural keys, legend computation |
| `src/components/StepWalkthrough.tsx` | Effect dependencies use stable keys |
| `src/components/VisualiserSearch.tsx` | Search filtering uses stable node snapshot |
| `src/components/FocusMode/FocusModeContent.tsx` | Focus graph calculation deps |
| `src/nodes/*/` | All node components wrapped in memo() |
| `src/edges/*/` | All edge components wrapped in memo() |

## Reference

Based on: "The Ultimate Guide to Optimize React Flow Project Performance" by Lukasz Jazwa. Key benchmarks from that article (100 nodes):
- Anonymous function on ReactFlow prop: 60 FPS -> 10 FPS (default), 2 FPS (heavy)
- Node depending on full nodes array via useStore: 60 FPS -> 12 FPS
- Adding React.memo to nodes: recovers to 50-60 FPS even with non-optimal parent
- Memoizing heavy node content: recovers to 60 FPS stable

Related Skills

Content Performance Explainer

16
from diegosouzapw/awesome-omni-skill

Diagnose and explain why e-commerce content is or isn't performing against KPIs, using causal analysis frameworks, funnel decomposition, and competitive benchmarking to generate actionable improvement recommendations.

performance-analytics

16
from diegosouzapw/awesome-omni-skill

Analyze marketing performance with key metrics, trend analysis, and optimization recommendations. Use when building performance reports, reviewing campaign results, analyzing channel metrics (email, social, paid, SEO), or identifying what's working and what needs improvement.

spring-boot-performance

16
from diegosouzapw/awesome-omni-skill

Guide for optimizing Spring Boot application performance including caching, pagination, async processing, and JPA optimization. Use this when addressing performance issues or implementing high-traffic features.

PostgreSQL Performance Optimization

16
from diegosouzapw/awesome-omni-skill

Production-grade PostgreSQL query optimization, indexing strategies, performance tuning, and modern features including pgvector for AI/ML workloads. Master EXPLAIN plans, query analysis, and database design for high-performance applications

performance

16
from diegosouzapw/awesome-omni-skill

Optimize web performance for faster loading and better user experience. Use when asked to "speed up my site", "optimize performance", "reduce load time", "fix slow loading", "improve page speed", or "performance audit".

performance-profiling

16
from diegosouzapw/awesome-omni-skill

Performance profiling principles. Measurement, analysis, and optimization techniques.

performance-optimizer

16
from diegosouzapw/awesome-omni-skill

Performance analysis, profiling techniques, bottleneck identification, and optimization strategies for code and systems. Use when the user needs to improve performance, reduce resource usage, or identify and fix performance bottlenecks.

godot-profile-performance

16
from diegosouzapw/awesome-omni-skill

Detects performance bottlenecks in Godot projects including expensive _process functions, get_node() calls in loops, instantiations in _process, and provides optimization suggestions with Godot profiler integration

astro-performance

16
from diegosouzapw/awesome-omni-skill

Core Web Vitals and performance optimization for Astro sites. LCP, CLS, INP optimization, bundle size, fonts, third-party scripts. Use for performance tuning.

arch-performance-optimization

16
from diegosouzapw/awesome-omni-skill

Use when analyzing and improving performance for database queries, API endpoints, or frontend rendering.

application-performance-performance-optimization

16
from diegosouzapw/awesome-omni-skill

Optimize end-to-end application performance with profiling, observability, and backend/frontend tuning. Use when coordinating performance optimization across the stack.

analyze-performance

16
from diegosouzapw/awesome-omni-skill

パフォーマンスボトルネックを特定し、最適化提案を提示