Agent Skills - Phần 11: React Performance Best Practices
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:
- Async Waterfall & Data fetching
- Bundle Size & Code splitting
- Server-side caching & RSC boundaries
- Client caching (SWR) & event listeners
- Rerender tối ưu (state/effect)
- Rendering & hydration
- 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ỉ
awaittrong 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,
awaitkhi thật cần trả về.
+ const sessionP = auth();
+ const configP = fetchConfig();
+ const session = await sessionP;
+ const config = await configP;
- Không nên:
awaittuầ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
Suspensevớ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/Sidebartự 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
indexké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
user50 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
useEffectmỗi lần mount.
- useEffect(() => {
- fetch('/api/users').then(setUsers);
- }, []);
4.2 🟢 Share global event listeners
- Nên: Dùng
useSWRSubscriptionhoặc abstraction để share listener global.
+ useSWRSubscription('global-keydown', subscribeKeydown);
- Không nên: Đăng ký
window.addEventListenertrong mỗi instance.
5. Rerender Tối Ưu (State & Effects)
5.1 🟡 Đọc state chỉ trong callback khi cần
- Nên: Đọc
location/searchtrự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
isMobileboolean.
+ 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
itemsbê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
divvà 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
useEffectmớ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 &&vì0là 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
.lengthtrước, combine nhiềufilter()/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 Waterfall | Dùng Promise.all, start early/await late, Suspense |
| Bundle Size | Dynamic import, tránh barrel, load conditional/intent-based |
| Server & RSC | cache(), LRU, giảm serialization qua boundary |
| Client Caching | SWR cho fetch, share event listeners |
| Rerender | Functional setState, narrow deps, memoized component |
| Rendering | content-visibility, hoist JSX, tránh flicker hydration |
| JS Perf & Advanced | Set/Map, cache, toSorted, early return, stable handler refs |
Nguồn dữ liệu: react-performance.csv — ui-ux-pro-max-skill.