Loading...
Loading...
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
npx skill4agent add ocrbase-hq/ocrbase react-best-practices| Priority | Category | Impact |
|---|---|---|
| 1 | Eliminating Waterfalls | CRITICAL |
| 2 | Bundle Size Optimization | CRITICAL |
| 3 | Server-Side Performance | HIGH |
| 4 | Client-Side Data Fetching | MEDIUM-HIGH |
| 5 | Re-render Optimization | MEDIUM |
| 6 | Rendering Performance | MEDIUM |
| 7 | JavaScript Performance | LOW-MEDIUM |
| 8 | Advanced Patterns | LOW |
async function Page() {
const data = await fetchData(); // Blocks everything
return <Component data={data} />;
}async function Page() {
const dataPromise = fetchData(); // Start immediately
return <Component dataPromise={dataPromise} />;
}const user = await getUser();
const posts = await getPosts();
const comments = await getComments();const [user, posts, comments] = await Promise.all([
getUser(),
getPosts(),
getComments(),
]);function Component() {
useEffect(() => {
fetchData().then(setData); // Too late
}, []);
}// In route loader or parent component
const dataPromise = fetchData();
function Component({ dataPromise }) {
const data = use(dataPromise);
}<Suspense fallback={<Skeleton />}>
<AsyncComponent />
</Suspense>import { Button } from "@/components"; // Pulls entire barrel
import { format } from "date-fns"; // Pulls entire libraryimport { Button } from "@/components/ui/button";
import { format } from "date-fns/format";const HeavyChart = dynamic(() => import("./Chart"), {
loading: () => <ChartSkeleton />,
ssr: false,
});// Load analytics only after interaction
const loadAnalytics = () => import("analytics").then((m) => m.init());
useEffect(() => {
window.addEventListener("click", loadAnalytics, { once: true });
}, []);// next.config.js
module.exports = {
experimental: {
optimizePackageImports: ["lodash", "date-fns", "@radix-ui/react-icons"],
},
};import { cache } from "react";
const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});import { LRUCache } from "lru-cache";
const cache = new LRUCache<string, Data>({ max: 100 });
async function getData(key: string) {
if (cache.has(key)) return cache.get(key);
const data = await expensiveComputation(key);
cache.set(key, data);
return data;
}// Passing entire objects when only ID needed
<ClientComponent user={user} /><ClientComponent userId={user.id} />import useSWR from "swr";
function Profile({ userId }) {
const { data, error, isLoading } = useSWR(`/api/user/${userId}`, fetcher);
if (isLoading) return <Skeleton />;
if (error) return <Error />;
return <UserCard user={data} />;
}const [state, setState] = useState(expensiveComputation());const [state, setState] = useState(() => expensiveComputation());import { startTransition } from "react";
function handleSearch(query) {
startTransition(() => {
setSearchResults(search(query));
});
}function Parent() {
const [count, setCount] = useState(0);
return (
<>
<Counter count={count} setCount={setCount} />
<ExpensiveList /> {/* Re-renders on every count change */}
</>
);
}function Parent() {
return (
<>
<CounterWithState />
<ExpensiveList />
</>
);
}const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);const handleClick = useCallback((id) => {
setItems((items) => items.filter((item) => item.id !== id));
}, []);.list-item {
content-visibility: auto;
contain-intrinsic-size: 0 50px;
}function AnimatedIcon() {
return (
<div style={{ contain: "layout" }}>
<AnimatedSVG />
</div>
);
}<div style={{ display: isVisible ? "block" : "none" }}>
<ExpensiveComponent />
</div>{
isVisible && <ExpensiveComponent />;
}// Use refs for multiple rapid updates
const counterRef = useRef<HTMLSpanElement>(null);
function increment() {
if (counterRef.current) {
counterRef.current.textContent = String(++count);
}
}items.forEach((item) => {
const related = otherItems.find((o) => o.id === item.relatedId);
});const otherItemsById = new Map(otherItems.map((o) => [o.id, o]));
items.forEach((item) => {
const related = otherItemsById.get(item.relatedId);
});// Prefer these for predictable React updates
const updated = items.with(index, newValue);
const filtered = items.toSpliced(index, 1);
const sorted = items.toSorted((a, b) => a.name.localeCompare(b.name));const value = useSyncExternalStore(
store.subscribe,
store.getSnapshot,
store.getServerSnapshot
);const [optimisticItems, addOptimisticItem] = useOptimistic(
items,
(state, newItem) => [...state, { ...newItem, pending: true }]
);