Skip to main content

Agent Skills - Phần 11: React Performance Best Practices

Tóm tắt

Tài liệu tóm tắt React/Next.js performance best practices dùng làm checklist cho Agent khi refactor hoặc review code, bao gồm 7 nhóm chính: Async Waterfall & Data fetching, Bundle Size & Code splitting, Server-side caching & RSC boundaries, Client caching (SWR) & event listeners, Rerender tối ưu, Rendering & hydration, và JS performance & advanced patterns. Mỗi mục có ví dụ cụ thể về nên làm và không nên làm để tối ưu hiệu năng ứng dụng React/Next.js.

Tài liệu này tóm tắt React/Next.js performance best practices dùng làm checklist cho Agent khi refactor hoặc review code.
Nguồn: react-performance.csv từ ui-ux-pro-max-skill.

Nhóm chính:

  1. Async Waterfall & Data fetching
  2. Bundle Size & Code splitting
  3. Server-side caching & RSC boundaries
  4. Client caching (SWR) & event listeners
  5. Rerender tối ưu (state/effect)
  6. Rendering & hydration
  7. JS performance & advanced patterns

Trong mỗi mục:
Nên có ví dụ + · Không nên có ví dụ -.


1. Async Waterfall & Data Fetching

1.1 🔴 Tránh await blocking trên mọi branch (Async Waterfall)

  • Nên: Chỉ await trong branch thực sự cần.
+ if (skip) return { skipped: true };
+ const data = await fetch();
  • Không nên: Await ở đầu function rồi mới kiểm tra branch.
- const data = await fetch();
- if (skip) return { skipped: true };

1.2 🔴 Promise.all cho async độc lập

  • Nên: Chạy song song khi không phụ thuộc nhau.
+ const [user, posts] = await Promise.all([
+ fetchUser(),
+ fetchPosts(),
+ ]);
  • Không nên: Await tuần tự cho các request độc lập.
- const user = await fetchUser();
- const posts = await fetchPosts();

1.3 🔴 Tối ưu API Route – start promises early, await late

  • Nên: Khởi tạo promise sớm, await khi thật cần trả về.
+ const sessionP = auth();
+ const configP = fetchConfig();
+ const session = await sessionP;
+ const config = await configP;
  • Không nên: await tuần tự trong handler API.
- const session = await auth();
- const config = await fetchConfig();

1.4 🔴 Dùng Suspense để tránh chặn cả page

  • Nên: Wrap phần async trong Suspense với skeleton.
+ <Suspense fallback={<Skeleton />}>
+ <DataDisplay />
+ </Suspense>
  • Không nên: Đợi fetch xong hết rồi mới render page.
- const data = await fetchData();
- return <DataDisplay data={data} />;

1.5 🟡 Parallel fetch bằng composition trong RSC

  • Nên: Để Header / Sidebar tự fetch → chạy song song trong tree.
+ export default function Page() {
+ return (
+ <>
+ <Header />
+ <Sidebar />
+ <Content />
+ </>
+ );
+ }
  • Không nên: Fetch từng phần tuần tự trong component cha.

2. Bundle Size & Code Splitting

2.1 🔴 Tránh barrel import cho lib lớn

  • Nên: Import trực tiếp icon/component cần dùng.
+ import Check from 'lucide-react/dist/esm/icons/check';
  • Không nên: Import từ barrel index kéo theo nhiều module không dùng.
- import { Check } from 'lucide-react';

2.2 🔴 Dynamic import cho component nặng

  • Nên: Lazy-load editor/chart/... bằng next/dynamic.
+ const Monaco = dynamic(() => import('./MonacoEditor'), { ssr: false });
  • Không nên: Import thẳng editor ở top – vào main bundle.
- import MonacoEditor from './MonacoEditor';

2.3 🟡 Defer third-party (analytics, logging)

  • Nên: Dynamic import sau hydration / khi cần.
+ const Analytics = dynamic(() => import('@vercel/analytics/react'), {
+ ssr: false,
+ });
  • Không nên: Nhét analytics vào bundle chính.
- import { Analytics } from '@vercel/analytics/react';

2.4 🔴 Conditional loading cho feature hiếm dùng

  • Nên: Chỉ load module nặng khi bật feature.
+ useEffect(() => {
+ if (enabled) {
+ import('./heavy');
+ }
+ }, [enabled]);
  • Không nên: Import module nặng mọi lúc.
- import './heavy';

3. Server-side Caching & RSC Boundaries

3.1 🟡 React.cache để dedupe fetch trong 1 request

  • Nên: Wrap fetcher bằng cache() để tránh gọi lặp lại trong tree.
+ export const getUser = cache(async () => {
+ return db.user.find();
+ });

3.2 🟡 LRU cache cho cross-request

  • Nên: Cache dữ liệu ít đổi (config, feature flags...) bằng LRU.
+ const cache = new LRUCache({ max: 1000, ttl: 5 * 60 * 1000 });

3.3 🔴 Giảm serialization giữa Server & Client Component

  • Nên: Truyền đúng field client cần, không pass cả object to.
+ <Profile name={user.name} avatarUrl={user.avatarUrl} />
  • Không nên: Pass full user 50 field qua boundary.
- <Profile user={user} />

3.4 🟡 Dùng after() cho work không block response (Next.js)

  • Nên: Logging/analytics chạy sau khi trả response.
+ after(async () => {
+ await logAction();
+ });
+ return Response.json(data);
  • Không nên: Đợi log xong mới trả response.
- await logAction();
- return Response.json(data);

4. Client Caching & Shared Event Listeners

4.1 🟡 Dùng SWR cho fetch client-side

  • Nên: useSWR để tự dedupe + cache + revalidate.
+ const { data } = useSWR('/api/users', fetcher);
  • Không nên: Tự fetch trong useEffect mỗi lần mount.
- useEffect(() => {
- fetch('/api/users').then(setUsers);
- }, []);

4.2 🟢 Share global event listeners

  • Nên: Dùng useSWRSubscription hoặc abstraction để share listener global.
+ useSWRSubscription('global-keydown', subscribeKeydown);
  • Không nên: Đăng ký window.addEventListener trong mỗi instance.

5. Rerender Tối Ưu (State & Effects)

5.1 🟡 Đọc state chỉ trong callback khi cần

  • Nên: Đọc location/search trực tiếp trong handler nếu không cần subscribe.
+ const handleClick = () => {
+ const params = new URLSearchParams(location.search);
+ // ...
+ };
  • Không nên: Subscribe vào state nặng chỉ để dùng trong handler.
- const params = useSearchParams();
- const handleClick = () => {
- params.get('ref');
- };

5.2 🟡 Dùng memo() cho phần expensive

  • Nên: Tách sub-component và memo.
+ const UserAvatar = memo(function UserAvatar({ user }) {
+ // expensive
+ return <Avatar ... />;
+ });
  • Không nên: Tính toán nặng trong component chính rồi early return.

5.3 🟢 Narrow dependency cho useEffect

  • Nên: Dependency là primitive (id) thay vì cả object.
+ useEffect(() => {
+ console.log(user.id);
+ }, [user.id]);
  • Không nên: Dùng object reference làm dependency.
- useEffect(() => {
- console.log(user.id);
- }, [user]);

5.4 🟡 Dùng state derived boolean thay vì state continuous

  • Nên: Subscribes vào isMobile boolean.
+ const isMobile = useMediaQuery('(max-width: 767px)');
  • Không nên: Truyền cả width lên mọi component.
- const width = useWindowWidth();
- const isMobile = width < 768;

5.5 🟡 Functional setState

  • Nên: Dùng dạng callback để tránh stale closure & dependency dư thừa.
+ setItems(curr => [...curr, newItem]);
  • Không nên: Dựa vào biến items bên ngoài.
- setItems([...items, newItem]);

5.6 🟡 Lazy init cho useState

  • Nên: Truyền function nếu init tốn kém.
+ const [index] = useState(() => buildSearchIndex(items));
  • Không nên: Gọi trực tiếp – chạy lại mỗi render.
- const [index] = useState(buildSearchIndex(items));

5.7 🟡 Dùng startTransition cho update không khẩn cấp

  • Nên: Mark update heavy như scroll, filter UI là transition.
+ startTransition(() => {
+ setScrollY(window.scrollY);
+ });
  • Không nên: Gây block UI trên mọi state change.
- setScrollY(window.scrollY);

6. Rendering & Hydration

6.1 🟢 Animate wrapper thay vì SVG trực tiếp

  • Nên: Bọc SVG trong div và animate wrapper cho GPU-friendly.
+ <div className="animate-spin">
+ <svg>...</svg>
+ </div>
  • Không nên: Add animation trực tiếp lên SVG.
- <svg className="animate-spin">...</svg>

6.2 🔴 Dùng content-visibility: auto cho list dài

  • Nên: Cho item off-screen để browser defer render.
+ .item {
+ content-visibility: auto;
+ contain-intrinsic-size: 0 80px;
+ }
  • Không nên: Render 1000 item full DOM không tối ưu.

6.3 🟢 Hoist static JSX ra ngoài component

  • Nên: Khai báo skeleton/static element ở module scope.
+ const skeleton = <div className="animate-pulse" />;
+ function C() {
+ return skeleton;
+ }
  • Không nên: Tạo lại JSX y chang mỗi render.
- function C() {
- return <div className="animate-pulse" />;
- }

6.4 🟡 Tránh flicker khi hydration (theme, layout)

  • Nên: Dùng inline script sync trước hydration (ví dụ theme).
+ <script
+ dangerouslySetInnerHTML={{
+ __html: "document.documentElement.dataset.theme = localStorage.theme || 'light'",
+ }}
+ />
  • Không nên: Đợi useEffect mới set theme → flash.
- useEffect(() => {
- setTheme(localStorage.theme);
- }, []);

6.5 🟢 Dùng ternary thay vì && cho số 0

  • Nên: Explicit null khi count = 0.
+ {count > 0 ? <Badge>{count}</Badge> : null}
  • Không nên: count &&0 là falsy nhưng có thể render '0'.
- {count && <Badge>{count}</Badge>}

6.6 🟡 Preserve state khi show/hide component đắt

  • Nên: Dùng wrapper (Activity-like) để giữ DOM/state.
+ <Activity mode={isOpen ? 'visible' : 'hidden'}>
+ <Menu />
+ </Activity>
  • Không nên: Unmount/remount gây mất state mỗi lần toggle.
- {isOpen && <Menu />}

7. JS Performance & Advanced Patterns

7.1 🟡 Batch thay đổi CSS/DOM

  • Nên: Toggle class hoặc set nhiều style một lần.
+ element.classList.add('highlighted');
  • Không nên: Set từng property một gây nhiều reflow.
- el.style.width = '100px';
- el.style.height = '200px';

7.2 🟡 Dùng Map/Set & cache cho lookup lặp lại

  • Nên: Index trước rồi lookup O(1).
+ const byId = new Map(users.map(u => [u.id, u]));
+ byId.get(id);
  • Không nên: .find() trong loop hoặc .includes() lặp lại.
- users.find(u => u.id === id);

7.3 🟡 Hoist RegExp & memoize kết quả

  • Nên: Khai báo RegExp & cache ngoài render.
+ const EMAIL_RE = /^[^@]+@[^@]+$/;
+ function validate(x) {
+ return EMAIL_RE.test(x);
+ }
  • Không nên: Tạo RegExp/slugify trong mọi render/hot path.

7.4 🟡 Dùng toSorted() thay vì sort() mutate

  • Nên: Giữ immutability khi sort.
+ const sorted = users.toSorted((a, b) =>
+ a.name.localeCompare(b.name),
+ );
  • Không nên: Dùng array.sort() làm mutate state.
- users.sort((a, b) => a.name.localeCompare(b.name));

7.5 🟢 Early return, length check, combine loop

  • Nên: Thoát sớm khi có thể, check .length trước, combine nhiều filter()/map() thành 1 vòng lặp.

7.6 🟢 Event handler refs & useLatest

  • Nên: Lưu callback trong ref / useEffectEvent để effect subscription stable.
  • Giảm: Re-subscribe event hoặc chạy lại effect nặng chỉ vì callback đổi reference.

Tóm tắt

NhómÝ chính
Async WaterfallDùng Promise.all, start early/await late, Suspense
Bundle SizeDynamic import, tránh barrel, load conditional/intent-based
Server & RSCcache(), LRU, giảm serialization qua boundary
Client CachingSWR cho fetch, share event listeners
RerenderFunctional setState, narrow deps, memoized component
Renderingcontent-visibility, hoist JSX, tránh flicker hydration
JS Perf & AdvancedSet/Map, cache, toSorted, early return, stable handler refs

Nguồn dữ liệu: react-performance.csvui-ux-pro-max-skill.