Skip to content
Back to skills

React Rendering Performance

ASecurity

React 19+ rendering performance: React Compiler diagnostics, profiler-driven optimization, useTransition for non-blocking updates, the new Activity and ViewTransition components, resource preloading APIs, and when to actually reach for useMemo/useCallback vs letting the Compiler handle it. Use when debugging slow renders, optimizing user-perceived performance, or implementing smooth navigation transitions in React 19+ applications.

  • 15 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 3, 2026
ai-agentsgobashreactnextjsdebuggingapifrontendsecurityperformance

Works with

  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 4 files and shows the line behind each finding

Scanned September 3, 2026

npx -y skills add opensite-ai/opensite-skills --skill react-rendering-performance --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of React Rendering Performance?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for React Rendering Performance
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/opensite-ai-react-rendering-performance/badge)](https://www.skillsdirectory.com/skills/opensite-ai-react-rendering-performance)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: react-rendering-performance
description: >
  React 19+ rendering performance: React Compiler diagnostics, profiler-driven
  optimization, useTransition for non-blocking updates, the new Activity and
  ViewTransition components, resource preloading APIs, and when to actually
  reach for useMemo/useCallback vs letting the Compiler handle it. Use when
  debugging slow renders, optimizing user-perceived performance, or implementing
  smooth navigation transitions in React 19+ applications.
compatibility: >
  Requires React 19+ code and, ideally, React DevTools Profiler or runtime
  traces for measurement.
metadata:
  opensite-category: frontend
  opensite-scope: shared
  opensite-visibility: public
---
# React Rendering Performance

## Skill Resources
- Activation and cross-agent notes: [references/activation.md](references/activation.md)
- Use `ultrathink` or the deepest available reasoning mode before changing architecture, security, migration, or performance-critical paths.
- Template: [templates/profiler-session.md](templates/profiler-session.md)

Performance work without measurement is guessing. The correct sequence: measure with the Profiler, identify the bottleneck type, then apply the right tool. Memoizing everything is not a strategy.

---

## React 19 Compiler: What Changed

React Compiler (stable in React 19) automatically memoizes components, values, and callbacks. It analyzes the component tree statically and inserts `useMemo`, `useCallback`, and `React.memo` where they would help — without you writing them.

### What the Compiler Handles Automatically

```jsx
// ❌ Old React 18: Manual memoization required
const expensiveResult = useMemo(() => compute(data), [data]);
const stableCallback = useCallback(() => onSelect(id), [id, onSelect]);
const MemoizedChild = React.memo(({ value }) => <div>{value}</div>);

// ✅ React 19 + Compiler: Write normal code, Compiler memoizes when beneficial
const expensiveResult = compute(data);  // Compiler inserts memo if compute is expensive
const handleSelect = () => onSelect(id); // Compiler stabilizes if beneficial
function Child({ value }) { return <div>{value}</div>; } // Compiler adds memo if needed
```

### When You Still Need Manual Memoization

The Compiler cannot memoize:
- Third-party library calls with opaque costs
- References that must be stable for external reasons (WebSocket listeners, event emitters)
- Computations the Compiler cannot prove are pure

```jsx
// Use manual memo for external listener stability
const stableHandler = useCallback((event) => {
  externalEventEmitter.emit('change', event);
}, []); // Compiler may not stabilize this without the hint

useEffect(() => {
  externalEventEmitter.on('input', stableHandler);
  return () => externalEventEmitter.off('input', stableHandler);
}, [stableHandler]);
```

### Diagnosing Compiler Output

```bash
# Install the Compiler (Next.js 15+ enables it automatically)
npm install --save-dev babel-plugin-react-compiler

# Check which components the Compiler skipped and why
# In browser console after enabling compiler logging:
# REACT_COMPILER_DEVTOOLS=true npm run dev
```

The Compiler skips components that:
- Mutate props or state directly
- Use `arguments` object
- Call hooks conditionally
- Have unrecognized patterns

Fix the anti-pattern to let the Compiler optimize it.

---

## Chrome DevTools: React Performance Tracks (React 19.2)

React 19.2 adds React-specific lanes to Chrome's Performance panel, showing scheduler priorities, render timing, and effect execution directly alongside browser-level data.

```
Performance Panel → Record → Interact → Stop
                                        ↓
Look for the "React" track group (new in 19.2):
  - "Render" lanes: component tree render time per update
  - "Commit" lanes: DOM mutation and effect duration
  - "Scheduler" lanes: task priority and yielding behavior
```

Correlate React render timing with "Long Tasks" in the browser track to find renders that block the main thread.

---

## React Profiler: Finding Real Bottlenecks

Use the React DevTools Profiler to find which component is slow before writing any optimization code.

```jsx
import { Profiler } from 'react';

function onRenderCallback(
  id,             // Component name
  phase,          // "mount" | "update" | "nested-update"
  actualDuration, // Time for this render in ms
  baseDuration,   // Estimated time without memoization
  startTime,
  commitTime,
) {
  if (actualDuration > 16) { // > 16ms = missed a frame
    console.warn(`[Profiler] Slow render in ${id}: ${actualDuration.toFixed(1)}ms (phase: ${phase})`);
  }
}

// Wrap the component tree you're investigating
<Profiler id="OrderList" onRender={onRenderCallback}>
  <OrderList orders={orders} />
</Profiler>
```

### Reading Profiler Output

- **`actualDuration` >> `baseDuration`**: Memoization is working but a dependency changed unnecessarily — track down the unstable reference
- **`actualDuration` ≈ `baseDuration`**: Component renders fully every time — check if it should be memoized or if its parent re-renders too often
- **High `baseDuration`**: The component's own render logic is expensive — profile the inner computation

---

## `useTransition` for Non-Blocking Updates

Marking a state update as a Transition tells React it's non-urgent — the browser can interrupt it to handle user input (like typing) without waiting for the transition to finish.

```jsx
import { useTransition, useState } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleSearch(input) {
    // Typing the query is urgent — update immediately
    setQuery(input);

    // Rendering 10,000 search results is non-urgent — can be interrupted
    startTransition(() => {
      setResults(filterResults(input, allData));
    });
  }

  return (
    <>
      <input value={query} onChange={e => handleSearch(e.target.value)} />
      {isPending ? (
        <div aria-label="Loading results...">Searching...</div>
      ) : (
        <ResultList results={results} />
      )}
    </>
  );
}
```

### React 19: `useTransition` Now Accepts Async Functions

```jsx
const [isPending, startTransition] = useTransition();

async function handleSubmit(formData) {
  startTransition(async () => {
    // Async actions in transitions: React keeps isPending=true until resolved
    const result = await submitToServer(formData);
    setServerResult(result);
  });
}
```

---

## `useActionState` for Async Form Performance

Replaces the `isLoading` / `isError` / `result` state pattern with a single hook. Eliminates redundant state updates that trigger unnecessary re-renders.

```jsx
import { useActionState } from 'react';

// ❌ Old pattern: 3 state variables, each triggers a render
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [result, setResult] = useState(null);

// ✅ React 19: single hook, single render per state change
const [state, submitAction, isPending] = useActionState(
  async (prevState, formData) => {
    const name = formData.get('name');
    const result = await createRestaurant({ name });
    return { success: true, restaurant: result };
  },
  null, // initial state
);
```

---

## `useOptimistic`: Instant UI Updates

For mutations where you're confident the server will succeed, apply the update immediately and roll back only on failure.

```jsx
import { useOptimistic } from 'react';

function ReviewList({ reviews, onAddReview }) {
  const [optimisticReviews, addOptimisticReview] = useOptimistic(
    reviews,
    (currentReviews, newReview) => [...currentReviews, newReview],
  );

  async function handleSubmit(formData) {
    const optimisticEntry = {
      id: crypto.randomUUID(),
      text: formData.get('text'),
      rating: Number(formData.get('rating')),
      pending: true,
    };

    addOptimisticReview(optimisticEntry); // Instant UI update

    try {
      await onAddReview(formData); // If this fails, React reverts automatically
    } catch {
      // React automatically rolls back to the original reviews array
    }
  }

  return (
    <ul>
      {optimisticReviews.map(r => (
        <li key={r.id} style={{ opacity: r.pending ? 0.6 : 1 }}>
          {r.text}
        </li>
      ))}
    </ul>
  );
}
```

---

## `useEffectEvent`: Stop Unnecessary Effect Re-runs

Effects that close over props/state re-run when those values change, even when the closure behavior doesn't need to change. `useEffectEvent` separates "event logic" from "effect dependencies" (React 19.2).

```jsx
import { useEffect, useEffectEvent } from 'react';

function Analytics({ userId, currentTheme }) {
  // ❌ Without useEffectEvent: theme changes cause reconnect
  useEffect(() => {
    const socket = connect(userId);
    socket.on('event', (data) => {
      // currentTheme is captured here, causing effect to re-run when theme changes
      logEvent({ data, theme: currentTheme });
    });
    return () => socket.disconnect();
  }, [userId, currentTheme]); // theme in deps = reconnect on theme change

  // ✅ useEffectEvent: theme reads are "event" logic, not effect deps
  const onEvent = useEffectEvent((data) => {
    logEvent({ data, theme: currentTheme }); // reads latest theme without being a dep
  });

  useEffect(() => {
    const socket = connect(userId);
    socket.on('event', onEvent); // stable reference, but always reads latest theme
    return () => socket.disconnect();
  }, [userId]); // only reconnects when userId changes
}
```

---

## `<Activity>`: Background Preloading and State Preservation

`<Activity>` (React 19.2) renders hidden UI — preloading data, CSS, and components for the next navigation destination without impacting visible performance. It also preserves form state when users navigate away.

```jsx
import { Activity } from 'react';

function App({ currentRoute }) {
  return (
    <>
      {/* Visible content */}
      <Activity mode={currentRoute === 'home' ? 'visible' : 'hidden'}>
        <HomePage />
      </Activity>

      {/* Preloaded but hidden — data fetches run in background */}
      <Activity mode={currentRoute === 'orders' ? 'visible' : 'hidden'}>
        <OrdersPage />
      </Activity>
    </>
  );
}
```

**`mode` values:**
- `"visible"`: Rendered and in the DOM
- `"hidden"`: Rendered but hidden — state preserved, data prefetched, not painted

Use `<Activity>` for navigation destinations the user is likely to visit next. Avoid wrapping large, rarely-visited pages — the background renders cost CPU.

---

## `<ViewTransition>`: Coordinated Animations (React 19.2 experimental)

`<ViewTransition>` integrates React's rendering with the browser's native View Transition API for smooth page-to-page animations.

```jsx
import { ViewTransition } from 'react';
import { startTransition } from 'react';

function NavLink({ href, children }) {
  return (
    <a
      href={href}
      onClick={(e) => {
        e.preventDefault();
        startTransition(() => {
          navigate(href); // Your router's navigate function
        });
      }}
    >
      {children}
    </a>
  );
}

// Wrap the animated element
function PageContent({ route }) {
  return (
    <ViewTransition>
      <div key={route}>
        {/* Content changes trigger view transition animation */}
        <RouteContent route={route} />
      </div>
    </ViewTransition>
  );
}
```

The view transition only fires when the state update is wrapped in `startTransition`. Non-transition updates render immediately without animation.

---

## Resource Preloading APIs

React 19 added `react-dom` APIs for preloading resources before they're requested, reducing waterfall latency:

```jsx
import { preload, preinit, prefetchDNS, preconnect } from 'react-dom';

function App() {
  // Preload font — triggers <link rel="preload"> immediately
  preload('/fonts/Inter.woff2', { as: 'font', crossOrigin: 'anonymous' });

  // Preconnect to external API — establishes TCP+TLS before first request
  preconnect('https://api.stripe.com');

  // Prefetch DNS — faster than nothing, cheaper than preconnect
  prefetchDNS('https://cdn.analytics.com');

  // Preinit a script — download AND execute immediately
  preinit('/scripts/analytics.js', { as: 'script' });

  return <App />;
}
```

These calls are **idempotent** — safe to call multiple times; the browser deduplicates. Call them as early as possible in the tree.

---

## Common Performance Mistakes

| Mistake | Symptom | Fix |
|---------|---------|-----|
| Manual `useMemo`/`useCallback` everywhere in React 19 | Over-memoization, harder-to-read code | Let the Compiler handle it; only add manual memo for external stability requirements |
| Expensive computation in render (no memo) | High `baseDuration` in Profiler | `useMemo` for pure computations; `startTransition` for deferred renders |
| State updates for urgent and non-urgent work combined | Typing feels sluggish | Split urgent updates (typing) from non-urgent (filtering results) with `useTransition` |
| New object/array literals in JSX props | Child re-renders every time | Move stable data outside component or use the Compiler; avoid `{}` inline |
| `useEffect` deps include theme/locale | Effect fires too often | `useEffectEvent` for values that are read but don't trigger reconnects |
| No `<Activity>` on predictable navigation destinations | Visible load flash on navigation | Wrap likely-next pages in `<Activity mode="hidden">` |
| Missing `key` on list items | React reconciles wrong elements | Always provide stable, unique keys — never array index for sortable/filterable lists |

Files in this skill

  • SKILL.md13.5 KB
  • agents/openai.yaml361 B
  • references/activation.md944 B
  • templates/profiler-session.md373 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.