React Best Practices logo

React Best Practices

Community
AvivK5498
react-best-practices

React and Next.js performance optimization patterns. Use BEFORE implementing any React code to ensure best practices are followed.

Overview

PublisherAvivK5498
RepositoryThe-Claude-Protocol
Skill namereact-best-practices
Stars
348
Forks
25
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by AvivK5498 on GitHub. Read the source before you install it.

Installation

Install the React Best Practices AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/AvivK5498/The-Claude-Protocol.git /tmp/The-Claude-Protocol
mkdir -p .claude/skills
cp -r /tmp/The-Claude-Protocol/templates/skills/react-best-practices .claude/skills/react-best-practices
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable React Best Practices in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads React Best Practices on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so React Best Practices is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

React Best Practices

Version 1.0.0 Source: Vercel Engineering (vercel-labs/agent-skills)

Note: This document is for agents and LLMs to follow when maintaining, generating, or refactoring React and Next.js codebases. Contains 40+ rules across 8 categories, prioritized by impact.


How to Use This Skill

Before implementing ANY React/Next.js code:

  1. Review the relevant sections based on what you're building
  2. Apply the patterns as you write code
  3. Use the "Incorrect" vs "Correct" examples as templates

Priority order: Eliminating Waterfalls > Bundle Size > Server-Side > Client-Side > Re-renders > Rendering > JS Perf > Advanced


Quick Reference: Critical Rules

Top 5 Rules (Always Apply)

  1. Promise.all() for independent operations - Never sequential awaits for independent data
  2. Avoid barrel file imports - Import directly from source files
  3. Dynamic imports for heavy components - Lazy-load Monaco, charts, etc.
  4. Parallel data fetching with component composition - Structure RSC for parallelism
  5. Minimize serialization at RSC boundaries - Only pass needed fields to client

1. Eliminating Waterfalls

Impact: CRITICAL - Waterfalls are the #1 performance killer.

1.1 Defer Await Until Needed

Move await into branches where actually used.

typescript
// BAD: blocks both branches
async function handleRequest(userId: string, skipProcessing: boolean) {
  const userData = await fetchUserData(userId)
  if (skipProcessing) return { skipped: true }
  return processUserData(userData)
}

// GOOD: only blocks when needed
async function handleRequest(userId: string, skipProcessing: boolean) {
  if (skipProcessing) return { skipped: true }
  const userData = await fetchUserData(userId)
  return processUserData(userData)
}

1.2 Promise.all() for Independent Operations

typescript
// BAD: 3 round trips
const user = await fetchUser()
const posts = await fetchPosts()
const comments = await fetchComments()

// GOOD: 1 round trip
const [user, posts, comments] = await Promise.all([
  fetchUser(),
  fetchPosts(),
  fetchComments()
])

1.3 Strategic Suspense Boundaries

tsx
// BAD: wrapper blocked by data
async function Page() {
  const data = await fetchData()
  return (
    <div>
      <Sidebar />
      <DataDisplay data={data} />
      <Footer />
    </div>
  )
}

// GOOD: wrapper shows immediately
function Page() {
  return (
    <div>
      <Sidebar />
      <Suspense fallback={<Skeleton />}>
        <DataDisplay />
      </Suspense>
      <Footer />
    </div>
  )
}

2. Bundle Size Optimization

Impact: CRITICAL - Reduces TTI and LCP.

2.1 Avoid Barrel File Imports

tsx
// BAD: loads 1,583 modules
import { Check, X, Menu } from 'lucide-react'

// GOOD: loads only 3 modules
import Check from 'lucide-react/dist/esm/icons/check'
import X from 'lucide-react/dist/esm/icons/x'
import Menu from 'lucide-react/dist/esm/icons/menu'

// ALTERNATIVE: Next.js 13.5+ config
// next.config.js
module.exports = {
  experimental: {
    optimizePackageImports: ['lucide-react', '@mui/material']
  }
}

2.2 Dynamic Imports for Heavy Components

tsx
// BAD: Monaco bundles with main chunk (~300KB)
import { MonacoEditor } from './monaco-editor'

// GOOD: Monaco loads on demand
import dynamic from 'next/dynamic'
const MonacoEditor = dynamic(
  () => import('./monaco-editor').then(m => m.MonacoEditor),
  { ssr: false }
)

2.3 Defer Non-Critical Libraries

tsx
// BAD: blocks initial bundle
import { Analytics } from '@vercel/analytics/react'

// GOOD: loads after hydration
import dynamic from 'next/dynamic'
const Analytics = dynamic(
  () => import('@vercel/analytics/react').then(m => m.Analytics),
  { ssr: false }
)

2.4 Preload on User Intent

tsx
function EditorButton({ onClick }: { onClick: () => void }) {
  const preload = () => {
    if (typeof window !== 'undefined') {
      void import('./monaco-editor')
    }
  }
  return (
    <button onMouseEnter={preload} onFocus={preload} onClick={onClick}>
      Open Editor
    </button>
  )
}

3. Server-Side Performance

Impact: HIGH

3.1 Minimize Serialization at RSC Boundaries

tsx
// BAD: serializes all 50 fields
async function Page() {
  const user = await fetchUser()  // 50 fields
  return <Profile user={user} />
}

// GOOD: serializes only needed fields
async function Page() {
  const user = await fetchUser()
  return <Profile name={user.name} avatar={user.avatar} />
}

3.2 Parallel Data Fetching with Component Composition

tsx
// BAD: Sidebar waits for Header's fetch
export default async function Page() {
  const header = await fetchHeader()
  return (
    <div>
      <div>{header}</div>
      <Sidebar />
    </div>
  )
}

// GOOD: both fetch simultaneously
async function Header() {
  const data = await fetchHeader()
  return <div>{data}</div>
}

async function Sidebar() {
  const items = await fetchSidebarItems()
  return <nav>{items.map(renderItem)}</nav>
}

export default function Page() {
  return (
    <div>
      <Header />
      <Sidebar />
    </div>
  )
}

3.3 Per-Request Deduplication with React.cache()

typescript
import { cache } from 'react'

export const getCurrentUser = cache(async () => {
  const session = await auth()
  if (!session?.user?.id) return null
  return await db.user.findUnique({ where: { id: session.user.id } })
})

3.4 Use after() for Non-Blocking Operations

tsx
import { after } from 'next/server'

export async function POST(request: Request) {
  await updateDatabase(request)

  // Log after response is sent
  after(async () => {
    const userAgent = (await headers()).get('user-agent')
    logUserAction({ userAgent })
  })

  return Response.json({ status: 'success' })
}

4. Client-Side Data Fetching

Impact: MEDIUM-HIGH

4.1 Use SWR for Automatic Deduplication

tsx
// BAD: no deduplication
function UserList() {
  const [users, setUsers] = useState([])
  useEffect(() => {
    fetch('/api/users').then(r => r.json()).then(setUsers)
  }, [])
}

// GOOD: multiple instances share one request
import useSWR from 'swr'
function UserList() {
  const { data: users } = useSWR('/api/users', fetcher)
}

5. Re-render Optimization

Impact: MEDIUM

5.1 Use Functional setState Updates

tsx
// BAD: requires state as dependency, risk of stale closure
const addItems = useCallback((newItems: Item[]) => {
  setItems([...items, ...newItems])
}, [items])

// GOOD: stable callback, no stale closures
const addItems = useCallback((newItems: Item[]) => {
  setItems(curr => [...curr, ...newItems])
}, [])

5.2 Use Lazy State Initialization

tsx
// BAD: runs on every render
const [settings] = useState(JSON.parse(localStorage.getItem('settings') || '{}'))

// GOOD: runs only once
const [settings] = useState(() => {
  const stored = localStorage.getItem('settings')
  return stored ? JSON.parse(stored) : {}
})

5.3 Use Transitions for Non-Urgent Updates

tsx
import { startTransition } from 'react'

function ScrollTracker() {
  const [scrollY, setScrollY] = useState(0)
  useEffect(() => {
    const handler = () => {
      startTransition(() => setScrollY(window.scrollY))
    }
    window.addEventListener('scroll', handler, { passive: true })
    return () => window.removeEventListener('scroll', handler)
  }, [])
}

5.4 Narrow Effect Dependencies

tsx
// BAD: re-runs on any user field change
useEffect(() => {
  console.log(user.id)
}, [user])

// GOOD: re-runs only when id changes
useEffect(() => {
  console.log(user.id)
}, [user.id])

6. Rendering Performance

Impact: MEDIUM

6.1 CSS content-visibility for Long Lists

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

6.2 Hoist Static JSX Elements

tsx
// BAD: recreates element every render
function Container() {
  return loading && <div className="animate-pulse h-20 bg-gray-200" />
}

// GOOD: reuses same element
const loadingSkeleton = <div className="animate-pulse h-20 bg-gray-200" />
function Container() {
  return loading && loadingSkeleton
}

6.3 Animate SVG Wrapper, Not SVG Element

tsx
// BAD: no hardware acceleration
<svg className="animate-spin">...</svg>

// GOOD: hardware accelerated
<div className="animate-spin">
  <svg>...</svg>
</div>

7. JavaScript Performance

Impact: LOW-MEDIUM

7.1 Build Index Maps for Repeated Lookups

typescript
// BAD: O(n) per lookup
items.filter(item => allowedIds.includes(item.id))

// GOOD: O(1) per lookup
const allowedSet = new Set(allowedIds)
items.filter(item => allowedSet.has(item.id))

7.2 Use toSorted() Instead of sort()

typescript
// BAD: mutates original array
const sorted = users.sort((a, b) => a.name.localeCompare(b.name))

// GOOD: creates new array
const sorted = users.toSorted((a, b) => a.name.localeCompare(b.name))

7.3 Early Return from Functions

typescript
// BAD: processes all items after finding error
function validateUsers(users: User[]) {
  let hasError = false
  for (const user of users) {
    if (!user.email) hasError = true
  }
  return hasError ? { valid: false } : { valid: true }
}

// GOOD: returns immediately on first error
function validateUsers(users: User[]) {
  for (const user of users) {
    if (!user.email) return { valid: false, error: 'Email required' }
  }
  return { valid: true }
}

8. Advanced Patterns

Impact: LOW

8.1 useEffectEvent for Stable Callbacks

tsx
import { useEffectEvent } from 'react'

function useWindowEvent(event: string, handler: () => void) {
  const onEvent = useEffectEvent(handler)
  useEffect(() => {
    window.addEventListener(event, onEvent)
    return () => window.removeEventListener(event, onEvent)
  }, [event])
}

Checklist Before Implementation

  • Independent async operations use Promise.all()
  • Heavy components use dynamic imports
  • RSC boundaries pass only needed fields
  • Suspense boundaries isolate data fetching
  • No barrel file imports for large libraries
  • State updates use functional form when depending on current state
  • Effects have narrow dependencies
  • Repeated lookups use Set/Map

References

Frequently asked questions

What does the React Best Practices AI skill do?

React and Next.js performance optimization patterns. Use BEFORE implementing any React code to ensure best practices are followed.

Why use React Best Practices on TypingMind?

Because you install it once and use it with any model. React Best Practices is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install React Best Practices in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/AvivK5498/The-Claude-Protocol/tree/main/templates/skills/react-best-practices. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use React Best Practices?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with React Best Practices?

As many as you like. As long as a model supports skills, you can use React Best Practices with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the React Best Practices AI skill free?

Yes. It is published on GitHub by AvivK5498 under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇