A full-stack best practices guide for React and Next.js (React 19+ / Next.js 15+), aimed at the 2025-2026 ecosystem. It includes 8 major categories and 60+ rules, ordered by impact level, and is suitable for writing new components, code review, performance optimization, and refactoring scenarios.
When to Use
Use this guide in the following scenarios:
- Writing new React components or pages
- Implementing data fetching logic
- Reviewing code performance issues
- Refactoring existing code
- Optimizing bundle size and load time
- Migrating to React 19+ / Next.js 15+ / Server Components
1. Eliminate Request Waterfalls (CRITICAL)
Request waterfalls are the number one performance killer. When multiple asynchronous operations execute serially, total latency = the sum of all operations.
1.1 Defer await to Where It Is Actually Needed
// ❌ 阻塞整个函数
async function Page() {
const user = await getUser();
const posts = await getPosts(); // 必须等 getUser 完成
return <Feed user={user} posts={posts} />;
}
// ✅ 并行发起,延迟 await
async function Page() {
const userPromise = getUser();
const postsPromise = getPosts();
const [user, posts] = await Promise.all([userPromise, postsPromise]);
return <Feed user={user} posts={posts} />;
}
1.2 Parallelize Independent Operations with Promise.all()
When multiple asynchronous operations do not depend on each other, always use Promise.all() to execute them in parallel.
// ❌ 串行:总时间 = T1 + T2 + T3
const user = await fetchUser(id);
const orders = await fetchOrders(id);
const recommendations = await fetchRecommendations(id);
// ✅ 并行:总时间 = max(T1, T2, T3)
const [user, orders, recommendations] = await Promise.all([
fetchUser(id),
fetchOrders(id),
fetchRecommendations(id),
]);
1.3 Place Suspense Boundaries Strategically
Use Suspense to split the page so that fast parts are displayed first and slow parts load asynchronously.
// ✅ 外壳立即可见,数据部分逐步流式加载
export default function DashboardPage() {
return (
<DashboardShell>
<Suspense fallback={<StatsSkeleton />}>
<StatsPanel />
</Suspense>
<Suspense fallback={<FeedSkeleton />}>
<ActivityFeed />
</Suspense>
</DashboardShell>
);
}
1.4 Avoid Waterfall Chains in API Routes
The same applies in API Routes / Server Actions — initiate all requests first, then await.
1.5 Maximize Parallelism When There Are Dependencies
When there are partial dependencies between operations, use a dependency graph to analyze which can be parallelized.
// ✅ user 和 config 并行;posts 依赖 user,但 config 不阻塞它
const [user, config] = await Promise.all([getUser(), getConfig()]);
const posts = await getPosts(user.id);
2. Bundle Size Optimization (CRITICAL)
2.1 Avoid Barrel File Imports
Barrel file(index.ts aggregate exports) can cause tree-shaking to fail, and a single icon may pull in the entire icon library.
// ❌ 拉入全部图标(200KB+)
import { Check } from "lucide-react";
// ✅ 直接从源文件导入
import Check from "lucide-react/dist/esm/icons/check";
Applies to all icon libraries, UI component libraries, and utility libraries.
2.2 Dynamically Import Heavy Components
For large components (editors, charts, maps, etc.), use next/dynamic or React.lazy。
// ✅ Monaco Editor (~300KB) 仅在需要时加载
import dynamic from "next/dynamic";
const CodeEditor = dynamic(() => import("@/components/CodeEditor"), {
loading: () => <EditorSkeleton />,
ssr: false,
});
2.3 Lazy Load Non-Critical Third-Party Libraries
Analytics scripts, error tracking, etc. should not block the first screen.
// ✅ hydration 完成后再加载分析脚本
useEffect(() => {
import("@analytics/core").then((mod) => mod.init());
}, []);
2.4 Conditionally Load Modules
Load the corresponding modules/data only when the feature is activated.
2.5 Preload Based on User Intent
Preload components that are about to be used on hover/focus.
// ✅ hover 时预加载,点击时立即可用
<button
onMouseEnter={() => import("@/components/SettingsPanel")}
onClick={() => setShowSettings(true)}
>
设置
</button>
3. Server-Side Performance (HIGH)
3.1 Server Actions Must Verify Identity
Server Actions are public endpoints and must verify authentication and authorization just like API Routes.
"use server";
export async function deletePost(postId: string) {
const session = await auth(); // ✅ 始终验证
if (!session) throw new Error("Unauthorized");
if (!await canDelete(session.user.id, postId)) throw new Error("Forbidden");
await db.post.delete({ where: { id: postId } });
}
3.2 Minimize Serialization at the RSC Boundary
Every prop passed from a Server Component to a Client Component will be serialized into the HTML. Pass only the fields the client needs.
// ❌ 传了整个 user 对象(包含敏感字段)
<UserCard user={user} />
// ✅ 只传需要的字段
<UserCard name={user.name} avatar={user.avatar} />
3.3 Use React.cache() for Deduplication Within a Request
When multiple components call the same data function in the same request, use React.cache() to avoid repeated execution.
import { cache } from "react";
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
// 同一请求中多次调用 getUser(id) 只执行一次
3.4 Use LRU Caching Across Requests
React.cache() is only effective within a single request. For data shared across requests, use LRU caching.
import { LRUCache } from "lru-cache";
const cache = new LRUCache<string, any>({ max: 500, ttl: 1000 * 60 * 5 });
export async function getConfig() {
const cached = cache.get("config");
if (cached) return cached;
const config = await db.config.findFirst();
cache.set("config", config);
return config;
}
3.5 Hoist Static I/O to the Module Level
Font files, logos, static configuration, etc. should not be read on every request.
// ✅ 模块加载时读取一次
const logoBuffer = await fs.readFile("./public/logo.png");
export function Logo() {
return <img src={`data:image/png;base64,${logoBuffer.toString("base64")}`} />;
}
3.6 Use after() for non-blocking operations
Operations that don't affect the response, such as logging and analytics, should use after() to be deferred until after the response is sent.
import { after } from "next/server";
export async function POST(request: Request) {
const result = await processOrder(request);
after(async () => {
await logAnalytics({ event: "order_created", orderId: result.id });
});
return Response.json(result);
}
3.7 Parallel data fetching + component composition
Take advantage of Server Component composition to let independent data fetches run in parallel automatically.
3.8 Avoid duplicate serialization
Don't pass both the raw array and derived state at the same time; let the client compute it itself.
4. Client-side data fetching (MEDIUM-HIGH)
4.1 Use SWR/TanStack Query for automatic deduplication
When multiple component instances subscribe to the same data, the request is shared automatically.
// ✅ 多个组件调用同一 key,只发一次请求
function useUser(id: string) {
return useSWR(`/api/users/${id}`, fetcher);
}
4.2 Deduplicate global event listeners
Use useSyncExternalStore or useSWRSubscription to share WebSocket/event sources.
4.3 Use passive event listeners
Add { passive: true }to scroll and touch events to eliminate browser scroll delay.
// ✅ 不阻塞滚动
element.addEventListener("touchstart", handler, { passive: true });
4.4 Add a version number to localStorage and minimize storage
// ✅ 带版本前缀 + 只存必要字段 + try-catch 包裹
const STORAGE_KEY = "app_prefs_v2";
function savePrefs(prefs: UserPrefs) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({
theme: prefs.theme,
lang: prefs.lang,
}));
} catch (e) {
// 存储已满或隐私模式
}
}
5. Re-render optimization (MEDIUM)
5.1 Compute derived state during render
Don't store computable values in state—compute them directly during render.
// ❌ 多余的 state + effect
const [items, setItems] = useState(data);
const [filteredItems, setFilteredItems] = useState([]);
useEffect(() => {
setFilteredItems(items.filter((i) => i.active));
}, [items]);
// ✅ 渲染期间直接计算
const [items, setItems] = useState(data);
const filteredItems = items.filter((i) => i.active);
5.2 Don't define components inside components
Inline components cause a full unmount + remount every time the parent component renders.
// ❌ 每次渲染都创建新组件
function Parent() {
function Child() { return <div>child</div>; } // 别这样做
return <Child />;
}
// ✅ 提取到外部,用 props 传递数据
function Child({ data }: { data: string }) {
return <div>{data}</div>;
}
function Parent() {
return <Child data="hello" />;
}
5.3 Use useRef to store frequently changing values that shouldn't trigger renders
For values that don't need to trigger UI updates, such as mouse position, scroll progress, and timer IDs, use ref instead of state.
5.4 Use useMemo/React.memo to control child component rendering
But note:Don't wrap simple expressions in useMemo。useMemo itself has overhead; simple calculations on booleans/numbers/strings aren't worth memoizing.
// ❌ 过度 memo
const isActive = useMemo(() => status === "active", [status]);
// ✅ 直接计算
const isActive = status === "active";
5.5 Use functional setState to avoid closure traps
// ❌ 可能使用过期的 count
setCount(count + 1);
// ✅ 始终基于最新值
setCount((prev) => prev + 1);
5.6 useState lazy initialization
Pass a function to useState, and it will only execute on the first render.
// ❌ 每次渲染都执行 expensive()
const [data, setData] = useState(expensiveComputation());
// ✅ 只在挂载时执行一次
const [data, setData] = useState(() => expensiveComputation());
5.7 Extract non-primitive default values as constants
// ❌ 每次渲染新建对象,memo 失效
function List({ config = { pageSize: 10 } }) { ... }
// ✅ 稳定引用
const DEFAULT_CONFIG = { pageSize: 10 };
function List({ config = DEFAULT_CONFIG }) { ... }
5.8 Narrow Effect dependencies
Subscribe to a derived boolean rather than a continuously changing value.
// ❌ value 每次变化都触发 effect
useEffect(() => { ... }, [value]);
// ✅ 只在 isAboveThreshold 变化时触发
const isAboveThreshold = value > 100;
useEffect(() => { ... }, [isAboveThreshold]);
5.9 Put interaction logic in event handlers
User behavior -> event handler, not expressed indirectly via state + effect.
5.10 Defer reading dynamic state
If searchParams/localStorage are only read in callbacks, don't subscribe to them at the render layer.
5.11 Use startTransition to mark non-urgent updates
// ✅ 搜索输入立即响应,结果列表延迟更新
function Search() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setQuery(e.target.value);
startTransition(() => {
setResults(searchItems(e.target.value));
});
}
return (
<>
<input value={query} onChange={handleChange} />
<ResultList results={results} />
</>
);
}
5.12 Extract into a Memo component to enable early returns
5.13 Things that shouldn't be done inside an Effect -> move them to event handlers
6. Rendering performance (MEDIUM)
6.1 Use CSS content-visibility to optimize long lists
/* ✅ 浏览器跳过屏幕外内容的渲染 */
.list-item {
content-visibility: auto;
contain-intrinsic-size: 0 80px;
}
6.2 Hoist static JSX outside the component
Extract JSX fragments that don't change into module-level constants to avoid recreating them on every render.
// ✅ 只创建一次
const EMPTY_STATE = <div className="empty">暂无数据</div>;
function List({ items }: { items: Item[] }) {
if (items.length === 0) return EMPTY_STATE;
return <ul>{items.map(...)}</ul>;
}
6.3 Prevent hydration flicker
For UI that depends on localStorage/cookie (such as dark mode), use a synchronous script to set it before React hydrates.
<!-- ✅ 在 <head> 中同步执行,hydration 前完成 -->
<script>
const theme = localStorage.getItem("theme") || "light";
document.documentElement.dataset.theme = theme;
</script>
6.4 Use the Activity component (React 19+)
For heavy components that frequently toggle between shown/hidden, use <Activity> to preserve state and DOM.
// ✅ 切换时保留状态,不重新挂载
import { Activity } from "react";
<Activity mode={activeTab === "editor" ? "visible" : "hidden"}>
<HeavyEditor />
</Activity>
6.5 Wrap SVG animations in a div
Doing CSS animations directly on SVG elements skips GPU acceleration. Wrap them in a div.
6.6 Optimize SVG precision
Use SVGO to reduce coordinate precision to 1 decimal place, reducing SVG size by 20-40%.
6.7 Always add defer/async to script tags
6.8 Use useTransition instead of manual loading state
// ❌ 手动管理 loading
const [isLoading, setIsLoading] = useState(false);
async function handleClick() {
setIsLoading(true);
await doSomething();
setIsLoading(false);
}
// ✅ 内置 isPending
const [isPending, startTransition] = useTransition();
function handleClick() {
startTransition(async () => {
await doSomething();
});
}
6.9 Use explicit ternary instead of && rendering
// ❌ count 为 0 时渲染 "0" 而非 null
{count && <Badge count={count} />}
// ✅ 明确处理 falsy 值
{count > 0 ? <Badge count={count} /> : null}
6.10 Use React DOM resource hints
import { prefetchDNS, preconnect, preload } from "react-dom";
// ✅ 预连接关键第三方域
preconnect("https://api.example.com");
preload("/fonts/inter.woff2", { as: "font", type: "font/woff2" });
6.11 suppressHydrationWarning for known differences
For known SSR/client differences such as dates and random IDs, use suppressHydrationWarning。
7. JavaScript Performance (LOW-MEDIUM)
7.1 Avoid layout thrashing
Batch style writes, then read layout properties. Interleaved reads and writes force synchronous reflow.
// ❌ 每次循环都触发 reflow
elements.forEach((el) => {
el.style.width = `${container.offsetWidth}px`; // 读 -> 写 -> 读 -> 写
});
// ✅ 先读,再批量写
const width = container.offsetWidth;
elements.forEach((el) => {
el.style.width = `${width}px`;
});
7.2 Use Map/Set instead of array lookups
The gap between O(1) and O(n) grows exponentially with large datasets.
// ❌ O(n) 查找
const user = users.find((u) => u.id === targetId);
// ✅ O(1) 查找
const userMap = new Map(users.map((u) => [u.id, u]));
const user = userMap.get(targetId);
7.3 Cache property access in loops
7.4 Cache repeated function call results
7.5 Cache Storage API calls
localStorage/sessionStorage are synchronous and blocking; use an in-memory cache to avoid repeated reads.
7.6 Combine multiple array iterations
// ❌ 两次迭代
const active = users.filter((u) => u.active).map((u) => u.name);
// ✅ 一次迭代
const active = users.reduce<string[]>((acc, u) => {
if (u.active) acc.push(u.name);
return acc;
}, []);
// ✅ 或用 flatMap
const active = users.flatMap((u) => (u.active ? [u.name] : []));
7.7 Check length before comparing arrays
7.8 Early return from functions
Return immediately once the result is determined; do not go through all branches.
7.9 Hoist regular expressions to module level
// ❌ 每次渲染都编译正则
function validate(email: string) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// ✅ 模块级编译一次
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function validate(email: string) {
return EMAIL_RE.test(email);
}
7.10 Use flatMap instead of filter + map
7.11 Use a loop instead of sorting to find min/max
O(n) vs O(n log n)。
7.12 Use toSorted() instead of sort()
toSorted() returns a new array without mutating the original array, avoiding React state mutation bugs.
7.13 Exit loops early
8. Advanced Patterns (LOW)
8.1 Module-level initialization instead of useEffect
// ❌ StrictMode 下执行两次,组件重新挂载时再次执行
useEffect(() => {
analytics.init();
}, []);
// ✅ 模块级,只执行一次
let initialized = false;
function initOnce() {
if (initialized) return;
initialized = true;
analytics.init();
}
initOnce();
8.2 Use Ref to store event handlers
Keep subscription references stable and avoid repeated resubscription when callbacks change.
const handlerRef = useRef(onMessage);
handlerRef.current = onMessage; // 每次渲染更新引用
useEffect(() => {
const handler = (e: MessageEvent) => handlerRef.current(e.data);
ws.addEventListener("message", handler);
return () => ws.removeEventListener("message", handler);
}, []); // 依赖数组为空——永不重新订阅
8.3 useEffectEvent(React 19+)
Access the latest value in callbacks without triggering Effect re-execution.
// ✅ React 19+ 实验 API
const onTick = useEffectEvent((time: number) => {
console.log(latestConfig, time); // 始终拿到最新 config
});
useEffect(() => {
const id = setInterval(() => onTick(Date.now()), 1000);
return () => clearInterval(id);
}, []); // 不需要依赖 config
9. React 19+ New Features Quick Reference
React 19 introduces several paradigm-level changes. The following are the key new APIs:
9.1 use() Hook
Read Promises and Context directly in components, replacing some useEffect + useState patterns.
// ✅ React 19: 直接在组件中 unwrap Promise
import { use } from "react";
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise); // 配合 Suspense 使用
return <div>{user.name}</div>;
}
9.2 Actions(useActionState + useFormStatus)
First-class support for form submission.
"use client";
import { useActionState } from "react";
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? "提交中..." : "提交"}</button>;
}
function LoginForm() {
const [state, formAction] = useActionState(loginAction, { error: null });
return (
<form action={formAction}>
<input name="email" type="email" required />
{state.error && <p className="error">{state.error}</p>}
<SubmitButton />
</form>
);
}
9.3 useOptimistic
Optimistic updates to improve perceived interaction speed.
const [optimisticMessages, addOptimistic] = useOptimistic(
messages,
(state, newMsg: string) => [...state, { text: newMsg, sending: true }]
);
async function sendMessage(text: string) {
addOptimistic(text); // 立即显示
await submitMessage(text); // 后台提交
}
9.4 Pass ref directly as a prop
In React 19, function components receive ref directly as a prop, no longer requiring forwardRef。
// ✅ React 19: 直接传递
function Input({ ref, ...props }: { ref: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
9.5 Native support for document metadata
// ✅ React 19: 组件内直接声明 <title>、<meta>、<link>
function BlogPost({ post }: { post: Post }) {
return (
<article>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
10. Server Components and Next.js 15+ in Practice
10.1 Component division principles
Server Component(默认) Client Component('use client')
───────────────────────── ─────────────────────────────
数据获取 事件处理(onClick 等)
敏感逻辑/密钥访问 useState / useEffect / useRef
数据库直连 浏览器 API(localStorage 等)
静态渲染 第三方仅客户端库
大型依赖(不发送到客户端) 实时交互组件
10.2 Composition pattern: Server wrapping Client
// ✅ Server Component 获取数据,Client Component 处理交互
// app/dashboard/page.tsx (Server Component)
export default async function DashboardPage() {
const stats = await getStats(); // 服务端直连数据库
return (
<div>
<h1>仪表盘</h1>
<StatsDisplay initialData={stats} /> {/* Client Component */}
</div>
);
}
// components/StatsDisplay.tsx
"use client";
export function StatsDisplay({ initialData }: { initialData: Stats }) {
const [data, setData] = useState(initialData);
// 客户端交互逻辑...
}
10.3 Next.js 15+ caching strategies
// 静态缓存(构建时)
fetch(url, { cache: "force-cache" });
// 按时间重新验证
fetch(url, { next: { revalidate: 3600 } });
// 动态不缓存
fetch(url, { cache: "no-store" });
// 按需重新验证
import { revalidateTag, revalidatePath } from "next/cache";
revalidateTag("posts");
revalidatePath("/blog");
10.4 Partial Prerendering(PPR)
Next.js 15+'s hybrid rendering mode: static shell + streaming dynamic content.
// next.config.ts
export default { experimental: { ppr: true } };
// 页面中:静态部分预渲染,Suspense 内动态流式加载
export default function ProductPage({ params }: { params: { id: string } }) {
return (
<ProductLayout>
{/* 这部分是静态的,预渲染到 HTML */}
<ProductHeader id={params.id} />
<Suspense fallback={<PriceSkeleton />}>
{/* 这部分是动态的,流式加载 */}
<DynamicPrice id={params.id} />
</Suspense>
</ProductLayout>
);
}
10.5 Server Actions best practices
"use server";
import { z } from "zod";
import { revalidatePath } from "next/cache";
const CreatePostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
});
export async function createPost(formData: FormData) {
// 1. 验证身份
const session = await auth();
if (!session) throw new Error("Unauthorized");
// 2. 验证输入
const parsed = CreatePostSchema.safeParse({
title: formData.get("title"),
content: formData.get("content"),
});
if (!parsed.success) return { error: parsed.error.flatten() };
// 3. 执行操作
await db.post.create({ data: { ...parsed.data, authorId: session.user.id } });
// 4. 更新缓存
revalidatePath("/posts");
return { success: true };
}
Capability boundary declaration
When the user's needs involve the following aspects,briefly remind once, then focus back on React code best practices:
| Need | Suggestion |
|---|---|
| UI beautification / visual design / page looks ugly | Recommend using the frontend-design skill (/frontend-design), specializing in interface visual optimization |
| CSS motion design / micro-interactions | Recommend using the frontend-design skill to handle the visual layer; this skill focuses on React code patterns |
Reminder method example:"For UI beautification, you might try the /frontend-design skill; it specializes in visual design. I'll focus on helping you write React code correctly and well."
Only remind once when first encountered; do not mention it repeatedly.