Communitygithub.com

React 效能優化

改編自 Vercel Engineering 的 React 與 Next.js 效能優化模式

React 效能優化 是什麼?

此技能提供了一套以優先級排序的 React 18/19 與 Next.js 效能優化規則,旨在協助開發者在程式碼審查與重構過程中做出正確的效能決策。它源自 Vercel Labs 的 `react-best-practices` 專案,並將其整理成易於遵循的決策樹指引。技能涵蓋了從消除瀑布請求、最佳化 bundle 大小,到伺服器端效能與客戶端渲染優化的完整面向,每個類別都標有明確的優先級,讓開發者能專注於影響最大的項目。支援的平台包括 React 18/19 與 Next.js(App Router 與 Pages Router),並提供了大量可直接套用的程式碼範例,幫助開發者診斷慢速頁面載入、互動卡頓、高 CPU 使用率以及 Core Web Vitals 的衰退。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/react-performance

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

React 效能優化 是做什麼的?

針對 React 18/19 與 Next.js 的效能優化模式,改編自 Vercel Labs react-best-practices(MIT 授權,v1.0.0)。此技能依優先級整理規則,並提供決策樹指引,供活躍的程式碼審查與重構使用。

啟用時機

  • 撰寫或審查 React/Next.js 效能相關程式碼時
  • 診斷頁面載入緩慢、互動卡頓或客戶端 CPU 使用率過高時
  • 審計 bundle 大小或 Lighthouse Core Web Vitals 衰退時
  • 消除 Server Components / API 路由中的瀑布請求時
  • 減少客戶端重新渲染時
  • 優化長列表、動畫或 hydration 時
  • 審查影響 app/、pages/、components/ 或資料層的 PR 中的優化選擇時

優先級索引

優先級類別前綴適用時機
1 — 極重要消除瀑布請求async-任何 await 後面緊接著獨立的 await 時
2 — 極重要Bundle 大小優化bundle-首次載入 JS、路由層級導入、第三方套件
3 — 高伺服器端效能server-RSC、Server Actions、API 路由、SSR
4 — 中高客戶端資料擷取client-SWR / TanStack Query / hooks 中的原始 fetch
5 — 中重新渲染優化rerender-高頻狀態更新、父-子元件扇出
6 — 中渲染效能rendering-長列表、動畫、hydration
7 — 中低JavaScript 效能js-熱門迴圈、頻繁的記憶體分配
8 — 低進階模式advanced-Effect 與事件整合、穩定的 refs

1. 消除瀑布請求(極重要)

「瀑布請求是頭號效能殺手」—— 每個連續的 await 都會增加完整的網路延遲。

在 await 之前檢查低成本條件

在等待遠端資料之前,先檢查同步條件(props、環境變數、硬編碼標記)。

// 錯誤
async function Page({ id }: { id: string }) {
  const flag = await getFlag("show-page");
  if (!flag || !id) return null;
  const data = await getData(id);
  // ...
}

// 正確 — 先用低成本同步條件短路
async function Page({ id }: { id: string }) {
  if (!id) return null;
  const flag = await getFlag("show-page");
  if (!flag) return null;
  const data = await getData(id);
}

延後 await 直到真正使用

將 await 移入使用它的分支。

// 錯誤 — 在決定是否需要資料之前就 await
const user = await getUser(id);
if (mode === "guest") return renderGuest();
return renderUser(user);

// 正確
if (mode === "guest") return renderGuest();
const user = await getUser(id);
return renderUser(user);

對獨立任務使用 Promise.all

// 錯誤 — 連續執行
const user = await getUser(id);
const posts = await getPosts(id);
const followers = await getFollowers(id);

// 正確 — 並行執行
const [user, posts, followers] = await Promise.all([
  getUser(id),
  getPosts(id),
  getFollowers(id),
]);

部分依賴 — 提早啟動,延後 await

// 正確 — 同時啟動所有 Promise,只在需要結果時才 await
const userP = getUser(id);
const postsP = getPosts(id);
const profile = await getProfile(id);
if (profile.private) return null;
const [user, posts] = await Promise.all([userP, postsP]);

使用 Suspense 進行串流

將 <Suspense> 邊界放置在靠近資料的位置,以便頁面能在較慢的子樹串流進來時,先渲染其能渲染的部分。權衡:內容送達時可能發生版面位移——預留空間(骨架或 min-height)。

Server Components:透過組合實現並行

// 錯誤 — 同一個元件內的兄弟 await 會順序執行
export default async function Page() {
  const user = await getUser();
  const cart = await getCart();
  return <View user={user} cart={cart} />;
}

// 正確 — 拆分為子元件,React 會並行執行它們
export default async function Page() {
  return (
    <View>
      <UserSection />
      <CartSection />
    </View>
  );
}

2. Bundle 大小優化(極重要)

直接導入,而非 barrel 檔案

Barrel index.ts 檔案會強迫 bundler 遍歷整個模組圖譜,即使 tree-shaking 會移除大部分內容。直接導入在許多實際應用程式中可節省 200-800ms 的首次載入 JS。

// 錯誤
import { Button, Card, Modal } from "@/components";

// 正確
import { Button } from "@/components/Button";
import { Card } from "@/components/Card";
import { Modal } from "@/components/Modal";

Next.js 13.5+ 有 Optimize Package Imports 可以針對列出的套件自動化此操作——請使用它;但對於未列出的套件,仍需要手動直接導入。

可靜態分析的路徑

// 錯誤 — 破壞了 bundler/追蹤分析
const mod = await import(`./pages/${name}`);

// 正確 — 每個分支明確指定
const mod = name === "home" ? await import("./pages/home") : await import("./pages/about");

為重量級元件使用動態導入

import dynamic from "next/dynamic";

const HeavyChart = dynamic(() => import("./HeavyChart"), {
  loading: () => <Skeleton />,
  ssr: false, // 僅限客戶端使用時
});

延後第三方腳本

在 hydration 之後載入分析、日誌、支援小工具。使用 next/script 搭配 strategy="afterInteractive"(預設)或 "lazyOnload"。

條件式模組載入

if (user.role === "admin") {
  const { AdminPanel } = await import("./admin/AdminPanel");
  // ...
}

在滑鼠懸停/聚焦時預載

在滑鼠懸停時觸發 <link rel="preload"> 或 import(),以便在使用者點擊時 bundle 已存在於快取中。

3. 伺服器端效能(高)

像 API 路由一樣對 Server Actions 進行身份驗證

每個 "use server" 函式都是一個公開端點。在 action 內部進行身份驗證與授權——永遠不要依賴呼叫它的 Client Component 的閘道。

"use server";
export async function deleteUser(formData: FormData) {
  const session = await getSession();
  if (!session?.user) throw new Error("Unauthorized");
  const targetId = String(formData.get("id"));
  if (session.user.role !== "admin" && session.user.id !== targetId) {
    throw new Error("Forbidden");
  }
  await db.user.delete({ where: { id: targetId } });
}

React.cache() 用於每個請求的去重

import { cache } from "react";

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

React.cache 在單個請求內進行去重。從三個 Server Components 呼叫 getUser("1") 在相同渲染中 = 一次資料庫查詢。

跨請求資料的 LRU 快取

對於不會因請求而改變的資料(配置、查找表),使用 LRU 快取或 unstable_cache 在 React 外部進行快取。

避免 RSC props 中的重複序列化

當一個 Server Component 將相同的資料渲染到多個 Client Components 時,資料會為每個消費者序列化一次。將 Client Component 提升並傳遞 children。

將靜態 I/O 提升至模組作用域

// 正確 — 在模組載入時執行一次
const fontData = readFileSync(fontPath);

export async function Page() {
  return <Banner font={fontData} />;
}

RSC/SSR 中不應有可變的模組層級狀態

伺服器上的模組狀態在所有請求之間共享——這會造成使用者之間的競爭條件。應使用請求作用域的儲存(headers()、cookies()、非同步上下文)來取代。

最小化傳遞給 Client Components 的資料

僅序列化 Client 需要的資料。在資料庫層級過濾欄位、分頁、投影。

使用每個項目的 Promise.all 並行化巢狀擷取

const users = await getUsers();
const enriched = await Promise.all(
  users.map(async (u) => ({ ...u, posts: await getPostsFor(u.id) })),
);

使用 after() 處理非阻塞工作

Next.js 15 的 after() 在回應發送後執行工作——日誌記錄、快取預熱、分析。

import { after } from "next/server";
export async function GET() {
  const data = await getData();
  after(() => logAnalytics(data));
  return Response.json(data);
}

4. 客戶端資料擷取(中高)

使用 SWR / TanStack Query 進行去重

多個元件呼叫 useUser(id) 應共享一個網路請求和一個快取條目。請使用 SWR 或 TanStack Query——永遠不要為了共享資料而自己實作 useEffect + fetch。

去重全域事件監聽器

// 錯誤 — 每個元件都添加自己的監聽器
useEffect(() => {
  window.addEventListener("scroll", handler);
  return () => window.removeEventListener("scroll", handler);
}, []);

// 正確 — 透過 hook + 全域 subject 共享單一監聽器
const useScroll = createScrollHook(); // 底層是單例 subject

為滾動使用被動監聽器

window.addEventListener("scroll", handler, { passive: true });

改善滾動流暢度;監聽器無法呼叫 preventDefault()。

localStorage:版本化 + 最小化

  • 始終儲存 version 欄位;在 schema 變更時更新版本號,並遷移或拋棄舊資料
  • 保持 payload 小——localStorage 是同步的,會阻塞主執行緒

5. 重新渲染優化(中)

不要訂閱僅在回呼中使用的狀態

// 錯誤 — 每次 count 改變時都會重新渲染
const count = useStore((s) => s.count);
const handler = () => doSomething(count);

// 正確 — 在呼叫時讀取一次
const handler = () => {
  const count = useStore.getState().count;
  doSomething(count);
};

將昂貴的計算提取到 memoized 元件中

// 正確 — 子元件僅在 `items` 改變時重新渲染
const Heavy = memo(function Heavy({ items }: { items: Item[] }) {
  return <Chart data={transform(items)} />;
});

提升預設的非基本型別 props

// 錯誤 — 每次渲染都建立新陣列,破壞 memo
<List items={items ?? []} />

// 正確
const EMPTY: Item[] = [];
<List items={items ?? EMPTY} />

在 effects 中使用基本型別依賴

// 錯誤 — 每次渲染都有新的物件標識
useEffect(() => {}, [{ id, name }]);

// 正確 — 基本型別
useEffect(() => {}, [id, name]);

訂閱衍生的布林值,而非原始值

// 錯誤 — 為任何購物車變更而重新渲染
const cart = useStore((s) => s.cart);
const hasItems = cart.length > 0;

// 正確 — 僅在空/非空狀態切換時重新渲染
const hasItems = useStore((s) => s.cart.length > 0);

在渲染期間推導值,絕不使用 useEffect

// 錯誤
const [full, setFull] = useState("");
useEffect(() => setFull(`${first} ${last}`), [first, last]);

// 正確
const full = `${first} ${last}`;

為穩定的回呼使用函數式 setState

// 正確
const increment = useCallback(() => setCount((c) => c + 1), []);

為昂貴的值使用懶加載狀態初始化器

const [tree] = useState(() => parseTree(largeInput));

避免為簡單的基本型別使用 memo

useMemo(() => x + 1, [x]) 是開銷。Memo 在物件標識和昂貴計算上才值得使用。

拆分具有獨立依賴的 hooks

// 錯誤 — 如果任一來源改變,兩個 selector 都會重新執行
const { a, b } = useSomething(source1, source2);

// 正確
const a = useA(source1);
const b = useB(source2);

將互動邏輯移至事件處理函式中

事件處理函式僅在使用者操作時執行——useEffect 則會在依賴變更時重新執行。

為非緊急更新使用 startTransition

const [pending, startTransition] = useTransition();
startTransition(() => setFilters(newFilters));

為昂貴的渲染使用 useDeferredValue

const deferredQuery = useDeferredValue(query);
const results = useMemo(() => expensiveSearch(deferredQuery), [deferredQuery]);

為暫態高頻值使用 useRef

對於經常變更但不應觸發重新渲染的值(時間戳、最後按鍵、累加器)。

不要在元件內部定義元件

// 錯誤 — 每次 Outer 渲染時 Inner 都是一個新元件
function Outer() {
  const Inner = () => <span />;
  return <Inner />;
}

每次渲染都會產生一個新的 Inner 型別,這會破壞協調機制並卸載子元件。

6. 渲染效能(中)

對包裝層進行動畫,而非 SVG 本身

對 SVG 周圍的 <div> 包裝層進行變形(transform)是由 GPU 加速的;對 SVG 本身進行變形則會觸發繪製(paint)。

為長列表使用 content-visibility: auto

.row { content-visibility: auto; contain-intrinsic-size: auto 80px; }

瀏覽器會跳過螢幕外的渲染——對於有數百行的列表來說是一大勝利。

提升靜態 JSX

const STATIC_HEADER = <h1>Title</h1>;
function Page() {
  return <>{STATIC_HEADER}<Body /></>;
}

SVG:降低座標精度

d="M10.123456,20.654321" → d="M10.12,20.65"。每個數字都佔用位元組;視覺差異小於像素。

透過內聯腳本實現無閃爍的 Hydration

對於 hydration 之前需要的值(主題、語言環境),在 React 掛載之前,內聯一個 <script> 來設定 document.documentElement.dataset.*。

精確抑制預期的 Hydration 不匹配

<time suppressHydrationWarning>{new Date().toLocaleString()}</time>

僅對已知會不同的葉節點使用——絕不能用在包含其他 children 的樹上。

使用 <Activity> 進行顯示/隱藏,而非掛載/卸載

React 19 的 <Activity mode="visible|hidden"> 會保持樹的狀態和 effects 的掛載,但隱藏起來——對於頁籤和手風琴元件來說,比卸載/重新掛載更便宜。

條件渲染使用三元運算子而非 &&

// 錯誤 — `0` 會被渲染為文字節點
{count && <Badge>{count}</Badge>}

// 正確
{count > 0 ? <Badge>{count}</Badge> : null}

為載入狀態使用 useTransition

將 startTransition 與 action 配對使用;React 會在計算下一個狀態時,顯示前一個 UI 並將 isPending 設為真。

React DOM 資源提示

import { preload, preconnect } from "react-dom";
preload("/api/critical", { as: "fetch" });
preconnect("https://api.example.com")

使用 lazy 進行程式碼分割

import { lazy } from "react";
const LazyComponent = lazy(() => import("./HeavyComponent"));

影像載入優化

import Image from "next/image";
<Image src="/large.jpg" width={800} height={600} priority />

使用 next/image 實現自動 WebP 轉換、延遲載入和尺寸最佳化。

為清單項目使用穩定的 keys

{items.map(item => <li key={item.id}>{item.name}</li>)}

避免使用索引作為 key,除非列表是靜態的且順序不會改變。

使用 shouldComponentUpdate 或 React.memo 進行跳過性渲染

const MemoizedList = React.memo(function List({ items }) {
  return <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
});

避免在渲染期間建立新的函式

// 錯誤 — 每次渲染都建立新的箭頭函式
<button onClick={() => doSomething(id)}>Click</button>

// 正確 — 使用 useCallback 或將函式定義在元件外部
const handleClick = useCallback(() => doSomething(id), [id]);
<button onClick={handleClick}>Click</button>

優化重新渲染的偵測

// 當只有部分資料改變時,使用 React.memo 避免不必要的重新渲染
const UserProfile = React.memo(function UserProfile({ user, onUpdate }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <button onClick={() => onUpdate(user.id)}>Update</button>
    </div>
  );
});

使用 useCallback 和 useMemo 避免不必要的重新計算

const filteredList = useMemo(() => {
  return items.filter(item => item.isActive).sort((a, b) => a.name.localeCompare(b.name));
}, [items]);

const handleClick = useCallback(() => {
  setCount(prev => prev + 1);
}, []);

在 useEffect 中清理副作用

useEffect(() => {
  const subscription = dataSource.subscribe();
  return () => subscription.unsubscribe(); // 清理
}, []);

使用 useRef 追蹤先前的值

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

在 SSR 期間避免瀏覽器專用 API

// 錯誤 — 在 SSR 期間使用 `window` 會導致錯誤
if (typeof window !== "undefined") {
  // 僅在客戶端執行
  const width = window.innerWidth;
}

使用 ErrorBoundary 處理渲染錯誤

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

優化大型表單的狀態管理

// 使用單一狀態物件而非多個 useState 呼叫
const [formState, setFormState] = useState({
  username: '',
  email: '',
  password: ''
});

const handleChange = (e) => {
  setFormState(prev => ({
    ...prev,
    [e.target.name]: e.target.value
  }));
};

避免在渲染期間讀取 localStorage

// 錯誤 — 同步讀取會阻塞主執行緒
const data = JSON.parse(localStorage.getItem('key'));

// 正確 — 使用 effect 或在事件處理函式中讀取
useEffect(() => {
  const data = JSON.parse(localStorage.getItem('key'));
  setData(data);
}, []);

此摘要提供了優化 React 應用程式效能的全面指南,涵蓋了從高層級架構到低層級實作細節的各個方面。遵循這些模式,您可以顯著改善應用程式的回應速度和使用者體驗。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

相關技能