NYC
skills/s-hiraoku/synapse-a2a/react-performance

react-performance

SKILL.md

React Performance

Systematic performance optimization for React and Next.js applications, organized by impact.

Priority 1: Eliminate Waterfalls (CRITICAL)

Sequential async operations are the single biggest performance killer. Fix these first.

Defer Await

Move await to the point of use, not the point of declaration.

// BAD: Sequential — total time = fetch1 + fetch2
async function Page() {
  const user = await getUser();
  const posts = await getPosts(user.id);
  return <Feed user={user} posts={posts} />;
}

// GOOD: Parallel where possible
async function Page() {
  const userPromise = getUser();
  const postsPromise = getPosts(); // if independent
  const [user, posts] = await Promise.all([userPromise, postsPromise]);
  return <Feed user={user} posts={posts} />;
}

Suspense Streaming

Wrap slow data behind <Suspense> so the shell renders instantly.

export default function Page() {
  return (
    <main>
      <Header />              {/* instant */}
      <Suspense fallback={<Skeleton />}>
        <SlowDataSection />   {/* streams in */}
      </Suspense>
    </main>
  );
}

Partial Dependencies

When promises have partial dependencies, start independent work immediately.

async function Dashboard() {
  const userPromise = getUser();
  const settingsPromise = getSettings(); // independent

  const user = await userPromise;
  const postsPromise = getPosts(user.id); // depends on user

  const [settings, posts] = await Promise.all([settingsPromise, postsPromise]);
  return <View user={user} settings={settings} posts={posts} />;
}

Priority 2: Bundle Size (CRITICAL)

Direct Imports

Never import from barrel files in production code.

// BAD: Pulls entire library
import { Button } from '@/components';
import { format } from 'date-fns';

// GOOD: Tree-shakeable
import { Button } from '@/components/ui/button';
import { format } from 'date-fns/format';

Dynamic Imports

Code-split heavy components that aren't needed on initial render.

import dynamic from 'next/dynamic';

const Chart = dynamic(() => import('@/components/chart'), {
  loading: () => <ChartSkeleton />,
  ssr: false,  // skip SSR for client-only components
});

const Editor = dynamic(() => import('@/components/editor'), {
  loading: () => <EditorSkeleton />,
});

Defer Third-Party Scripts

Load analytics, logging, and non-critical scripts after hydration.

// BAD: Blocks hydration
import { analytics } from 'heavy-analytics';
analytics.init();

// GOOD: Load after hydration
useEffect(() => {
  import('heavy-analytics').then(({ analytics }) => analytics.init());
}, []);

Preload on Intent

Preload resources when user shows intent (hover, focus).

function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
  const router = useRouter();
  return (
    <Link
      href={href}
      onMouseEnter={() => router.prefetch(href)}
      onFocus={() => router.prefetch(href)}
    >
      {children}
    </Link>
  );
}

Priority 3: Server-Side Performance (HIGH)

Request-Scoped Deduplication

Use React.cache() to deduplicate data fetches within a single request.

import { cache } from 'react';

export const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } });
});

// Called in layout.tsx AND page.tsx — only one DB query

Minimize Serialization

Only pass the data client components actually need.

// BAD: Serializes entire user object
<ClientAvatar user={user} />

// GOOD: Pass only what's needed
<ClientAvatar name={user.name} avatarUrl={user.avatarUrl} />

Non-Blocking Background Work

Use after() for work that shouldn't block the response.

import { after } from 'next/server';

export async function POST(request: Request) {
  const data = await processRequest(request);

  after(async () => {
    await logAnalytics(data);
    await sendNotification(data);
  });

  return Response.json(data); // returns immediately
}

Priority 4: Re-render Prevention (MEDIUM)

Derived State

Compute derived values during render, never in effects.

// BAD: Extra render cycle
const [items, setItems] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => setCount(items.length), [items]);

// GOOD: Derive during render
const [items, setItems] = useState([]);
const count = items.length;

Stable References

Use callback-based setState and extract callbacks outside render.

// BAD: New function every render
<Button onClick={() => setCount(count + 1)} />

// GOOD: Stable reference
<Button onClick={() => setCount(c => c + 1)} />

Primitive Dependencies

Use primitives in dependency arrays to avoid false positives.

// BAD: Object reference changes every render
useEffect(() => { ... }, [config]);

// GOOD: Primitive values are stable
useEffect(() => { ... }, [config.apiUrl, config.timeout]);

Lazy State Initialization

Pass initializer functions for expensive initial state.

// BAD: Runs every render
const [data, setData] = useState(expensiveParse(raw));

// GOOD: Runs only on mount
const [data, setData] = useState(() => expensiveParse(raw));

Memoize Expensive Components

Extract heavy computation into memoized child components.

const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) {
  return items.map(item => <ComplexItem key={item.id} item={item} />);
});

useTransition for Deferrable Updates

Use startTransition for non-urgent state updates.

const [isPending, startTransition] = useTransition();

function handleSearch(query: string) {
  setInputValue(query);                    // urgent: update input
  startTransition(() => setResults(search(query))); // deferrable: update results
}

Priority 5: Rendering Performance (LOW-MEDIUM)

Content Visibility

Apply CSS content-visibility for long scrollable lists.

.list-item {
  content-visibility: auto;
  contain-intrinsic-size: 0 80px;
}

Hoist Static JSX

Extract JSX that doesn't depend on props/state outside the component.

// BAD: Recreated every render
function Layout({ children }) {
  return (
    <div>
      <footer><p>Copyright 2026</p></footer>
      {children}
    </div>
  );
}

// GOOD: Created once
const footer = <footer><p>Copyright 2026</p></footer>;
function Layout({ children }) {
  return <div>{footer}{children}</div>;
}

Conditional Rendering

Prefer ternary over && to avoid rendering 0 or "".

// BAD: Renders "0" when count is 0
{count && <Badge count={count} />}

// GOOD: Explicit boolean check
{count > 0 ? <Badge count={count} /> : null}

Quick Reference

Issue Fix Priority
Sequential fetches Promise.all() / Suspense CRITICAL
Barrel imports Direct path imports CRITICAL
Large initial bundle next/dynamic CRITICAL
Redundant DB calls React.cache() HIGH
Over-serialized props Pass primitives only HIGH
Derived state in useEffect Compute during render MEDIUM
Unstable callbacks useCallback / callback setState MEDIUM
Long lists Virtualization + content-visibility MEDIUM
Non-urgent updates useTransition MEDIUM
Weekly Installs
12
First Seen
9 days ago
Installed on
gemini-cli12
codex12
kimi-cli12
opencode12
claude-code11
amp11