| name | react-best-practices |
| description | Trigger: Next.js App Router, data fetching, Server vs Client components, images, fonts, SEO metadata, Suspense. Next.js 13+ best practices. Skip: non-Next.js React questions. |
| author | Gonzalo Astudillo |
| version | 1.0.0 |
| date | "2026-04-14T00:00:00.000Z" |
| user-invocable | true |
React Best Practices (Next.js / Vercel)
Hard Rules
- Never use
"use client" at page or layout level — always push it to the leaf component
- Never use
useEffect for server-side data fetching — use async/await in Server Components
- Never use
<img> — always next/image
- Never load fonts from a CDN — always
next/font
- Never use manual
<head> tags for metadata — always the Metadata API
- Never create a Route Handler for internal data — fetch in Server Components or use Server Actions
Golden Rule: Server Components by Default
Every component is a Server Component unless it needs:
useState / useReducer / useEffect
- Browser-only APIs (
window, document, localStorage)
- Event listeners (
onClick, onChange)
- Third-party libraries that require client context
If none of the above → Server Component. No "use client".
"use client" Placement
Push "use client" as deep as possible — to the leaf that actually needs it, never to a layout or page.
// Bad — marks entire page as client
"use client"
export default function Page() { ... }
// Good — only the interactive leaf is client
export default function Page() { // Server Component
return <Layout><InteractiveForm /></Layout>
}
// interactive-form.tsx
"use client"
export function InteractiveForm() { ... }
Data Fetching
Fetch data directly in Server Components with async/await. No useEffect, no SWR, no React Query for server-side data.
export default async function UserProfile({ id }: { id: string }) {
const user = await db.users.findById(id)
return <Profile user={user} />
}
For client-side data (mutations, real-time): use Server Actions or Route Handlers.
Images
Always use next/image. Never <img>.
import Image from "next/image"
<Image src="/hero.jpg" alt="Hero" width={800} height={400} priority />
Add priority for above-the-fold images. Define sizes for responsive images.
Fonts
Always use next/font. Never load fonts from CDN in <head>.
import { Inter } from "next/font/google"
const inter = Inter({ subsets: ["latin"] })
export default function Layout({ children }) {
return <html className={inter.className}>{children}</html>
}
Metadata
Use the Metadata API — never manual <head> tags.
export const metadata = {
title: "My App",
description: "My app description",
}
Suspense & Loading States
Use loading.tsx for route-level loading states. Use <Suspense> for component-level.
export default function Loading() { return <DashboardSkeleton /> }
<Suspense fallback={<ChartSkeleton />}>
<SlowChart /> {}
</Suspense>
Error Handling
Use error.tsx for route-level errors. Must be a Client Component.
"use client"
export default function Error({ error, reset }: { error: Error, reset: () => void }) {
return <div><p>{error.message}</p><button onClick={reset}>Retry</button></div>
}
Route Handlers
Only use Route Handlers (app/api/route.ts) for:
- Webhooks from external services
- APIs consumed by external clients
- OAuth callbacks
Do not create Route Handlers for internal data fetching — fetch directly in Server Components or use Server Actions.
Supporting Files
- references/server-vs-client.md — Decision tree: when to use Server vs Client Component
- references/patterns.md — Common patterns: Server Actions, parallel data fetching, caching