---
name: vercel-react-best-practices
description: "React + Next.js performance — RSC patterns, partial pre-rendering, edge config, ISR, Core Web Vitals. The performance-optimization patterns from Vercel's React engineering team: Server Components vs Client, partial pre-rendering (PPR), edge runtime decisions, ISR + revalidation, image optimization, fonts, and the Core Web Vitals targets that move LCP/INP/CLS."
license: MIT
---

# Vercel React Best Practices

A working playbook for shipping fast Next.js App Router applications, distilled from the patterns Vercel's React engineering team uses in production. It focuses on the decisions that actually move Core Web Vitals: where rendering happens, what runs at the edge, how aggressively you cache, and how the asset pipeline is configured. Aimed at engineers building or auditing Next.js 14/15 apps who want concrete guidance instead of trial-and-error.

## When to use it

- You're deciding whether a component should be a Server Component or a Client Component and keep guessing.
- LCP is slow because too much JavaScript ships, or INP is poor from oversized client bundles and hydration.
- You want to adopt Partial Prerendering (PPR) or `revalidate`-based ISR but aren't sure where the boundaries go.
- A CLS regression appeared after adding images, fonts, or ads.
- You're choosing between the Node.js and Edge runtimes for a route or middleware.

## What it does

The skill applies these patterns to your code and review comments:

- **Server vs Client boundaries** — keep `"use client"` as low in the tree as possible; pass Server Components as `children` into client wrappers instead of importing them.
- **Partial Prerendering** — `export const experimental_ppr = true` so a static shell streams instantly while dynamic holes resolve inside `<Suspense>`.
- **Caching & ISR** — `export const revalidate = N`, `fetch(url, { next: { revalidate, tags } })`, and on-demand `revalidateTag()` / `revalidatePath()` in Server Actions or route handlers.
- **Runtime selection** — `export const runtime = "edge"` for low-latency, lightweight routes; Node for heavy deps or native modules.
- **Assets** — `next/image` with explicit `sizes`/`priority`, and `next/font` to self-host fonts and eliminate layout shift.
- **CWV targets** — LCP < 2.5s, INP < 200ms, CLS < 0.1, validated with `@vercel/speed-insights`.

## Usage example

```tsx
// app/dashboard/page.tsx
import { Suspense } from "react";

export const experimental_ppr = true; // static shell + streamed dynamic parts

export default function Page() {
  return (
    <main>
      <Header />                       {/* prerendered instantly */}
      <Suspense fallback={<Skeleton />}>
        <LiveStats />                  {/* dynamic, streamed in */}
      </Suspense>
    </main>
  );
}
```

## Tips & gotchas

- PPR needs `ppr: "incremental"` in `next.config.js` before per-route `experimental_ppr` works — it's opt-in by route.
- `next/dynamic` with `ssr: false` only defers hydration; it does **not** shrink the server-rendered HTML. Move the boundary instead.
- Reaching for `cookies()`, `headers()`, or `searchParams` opts a route into dynamic rendering — wrap that subtree in `<Suspense>` so PPR still prerenders the rest.
- Set `sizes` on every responsive `next/image`; without it the browser downloads the largest candidate and tanks LCP.

## FAQ

**Edge or Node runtime?** Edge for short, globally-distributed reads with small bundles; Node when you need full Node APIs, large dependencies, or longer compute.

**ISR vs PPR?** ISR controls *how often* a page regenerates; PPR controls *which parts* render statically vs dynamically within a single request. They compose.

**How do I measure INP in the field?** Add `@vercel/speed-insights`; it reports real-user LCP, INP, and CLS per route on Vercel.
