react-patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited react-patterns (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Practical rules for writing fast, predictable React and Next.js code. Patterns are grouped by impact so when you have to choose what to fix first, start at the top. Most slowness in React apps is architectural — waterfalls, wrong boundaries, bundle bloat — not missing memo on a leaf component.
This skill describes patterns and principles in its own words. For deeper background, the React and Next.js docs are the source of truth (see Further Reading). Treat the examples here as illustrations, not copy-paste snippets.
Run the react patterns checklist alongside this process for a quick pass. Pair with [[perf-budget]] to measure before optimizing, [[browser-checks]] to verify in a real browser, [[ui-craft]] for interaction quality, [[micro-interactions]] for press states and view transitions, [[caching-strategy]] when adding cross-request caches, and [[accessibility]] when patterns affect focus or announcements.
Skip as the primary skill when the change has no React/Next surface (pure SQL, CLI, unrelated backend). Not a substitute for [[perf-budget]] — profile and set a budget before micro-optimizing.
Work top-down. The earlier categories almost always matter more than the later ones.
In the App Router, Server Components are the default. A Client Component ('use client') is needed only when the tree uses interactivity, browser-only APIs, or state/effect hooks.
| Put on the server | Keep on the client |
|---|---|
| Data fetching, secrets, DB/API calls | onClick, onChange, drag-and-drop |
| Large dependencies that never need the browser | useState, useEffect, most hooks |
| Static or slow-changing markup | Browser APIs (localStorage, window, observers) |
Streaming via Suspense | Third-party widgets that require the DOM |
Rules:
server-only modules in client bundles.
serialize cleanly; pass IDs and plain data, fetch or mutate on the correct side.
props through five layers when a Server Component child can fetch directly.
// Prefer: page shell is server; button is a small client leaf
export default async function Page() {
const data = await getData(); // server
return (
<>
<StaticSummary data={data} />
<LikeButton itemId={data.id} /> {/* 'use client' inside */}
</>
);
}A waterfall is any sequence where request B only starts after request A finishes, even though B did not need A's result. Each hop adds a full round-trip of latency.
Promise.all for fullyindependent work.
await, so you fail fast without paying for a network call.
await at the point youconsume the value.
await parent data before starting child fetches that don't depend on it —parallelize across the tree with separate async Server Components wrapped in Suspense.
Suspense so the shellships immediately and the slow part fills in later.
// Avoid: sequential, even though the two reads are unrelated
const user = await getUser(id);
const posts = await getPosts(id);
// Prefer: both in flight at once
const [user, posts] = await Promise.all([getUser(id), getPosts(id)]);// Avoid: child waits for parent await before its fetch starts
export default async function Page() {
const header = await getHeader();
return (
<>
<Header data={header} />
<SlowFeed /> {/* async inside, but only mounts after header resolves */}
</>
);
}
// Prefer: stream independent regions
export default function Page() {
return (
<>
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
<Suspense fallback={<FeedSkeleton />}>
<SlowFeed />
</Suspense>
</>
);
}Every kilobyte of JavaScript is downloaded, parsed, and executed before the page is interactive.
chunk.
next/dynamic or React.lazy; use { ssr: false }only when the component truly cannot render on the server.
interaction (next/script with strategy="lazyOnload" or on user intent).
the bundler cannot follow.
<Link prefetch> (default in viewport) or on hover/focusfor heavy navigations.
// Avoid: pulls entire icon library
import { Icon } from '@/components/icons';
// Prefer: direct import
import { CheckIcon } from '@/components/icons/CheckIcon';internals ([[hardening]]).
React.cache() (or equivalent request-scoped memo) so thesame data is not fetched twice in one render tree.
fetch with next: { revalidate }, cachetags, or framework cache helpers — for data that is expensive and changes slowly. Document invalidation (revalidateTag, revalidatePath) on every write path.
bandwidth and hydration time.
requests and leak data between users.
request.
empty states for missing resources — fail fast on the server.
import { cache } from 'react';
// Deduped within a single request/render pass
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});user-specific post-hydration data, or polling — not the initial page payload.
same key share one request; configure staleTime/gcTime explicitly.
component instance.
preventDefault, so scrolling stayssmooth.
localStorage; validate shape on read — corrupt storageshould not crash the tree.
URL/search params are ideal for shareable, bookmarkable UI state.
functional updater) instead.
useEffect/useMemo; objects and arrays recreated every renderdefeat comparison and retrigger effects.
useState with a function so the work runs once, not on every render.subtree.
up — prefer these over manual debounce for render-bound work.
don't wrap every primitive prop.
on any field change.
// Avoid: recomputed every render
const [items] = useState(buildExpensiveList());
// Prefer: lazy initializer runs once
const [items] = useState(() => buildExpensiveList());// Avoid: effect mirrors props → state
useEffect(() => { setCount(initialCount); }, [initialCount]);
// Prefer: derive, or reset with a key on the parent
const count = deriveCount(props);
// or <Counter key={userId} initialCount={initialCount} />is genuinely large (thousands) — measure before adding virtualization complexity.
id), not array index, when items reorder, insert, or delete.preload/preconnect) for critical assets; defer/async on scripts.Date.now(), windowsize) during SSR — gate on mount or use suppressHydrationWarning only for known-safe text (e.g. timestamps).
cause layout shift ([[ui-craft]], [[browser-checks]]).
without JS; still validate and authorize server-side.
useActionState — don't rely on thrown errors alone for expected validation failures.
cached reads — missing invalidation is a stale-data bug ([[caching-strategy]]).
responds — roll back on failure with a clear message.
'use client';
async function action(prevState, formData) {
'use server';
// validate, authorize, mutate, revalidateTag('items')
return { ok: true };
}Effects are for syncing with external systems (DOM subscriptions, network bridges, non-React widgets) — not for transforming data or responding to user events.
| Use an effect for | Don't use an effect for |
|---|---|
| Subscribing to external store/event | Deriving state from props/state |
| Integrating non-React library | Handling clicks/submits (event handlers) |
| Syncing with browser API after mount | Fetching data RSC can fetch on first paint |
| Logging/analytics side channel | Resetting state when props change (use key) |
Effect Event pattern (useEffectEvent in React 19+) — don't omit dependencies that should trigger re-sync.
Suspense with a meaningful fallback — the fallback is what userssee during streaming; make it structurally similar to the final UI.
Suspense when only part ofthe page is slow.
(retry, go home); log the error server-side ([[observability]]).
action/handler and return user-visible errors.
waiting for the slowest query.
These matter in hot paths and large loops — measure before micro-optimizing ([[perf-budget]]).
length before expensive comparisons.toSorted, flatMap) for clarity without extra passes.parent render.
per mount in every root component.
dependencies.
memo/useMemo may be redundant — still applyarchitectural patterns (boundaries, waterfalls, bundle size); verify with profiler, don't delete all memoization blindly.
compound fast across layout, page, and child components.
the bundle analyzer, don't assume.
memo/useMemo adds comparison cost and complexity;memoize measured-expensive work, not primitives.
the longer they live.
'use client' at the top." — One hook or click handler rarely justifies shipping theentire page as client JS; split into a leaf.
useEffect — it's simpler." — You pay with an extra client round-trip, loading flash,and no SSR for that data.
remove the effect and the bug class goes away.
row and bugs are subtle.
awaits where later calls don't use earlier results'use client' on a page/layout that is mostly static data and markupimport { X } from 'big-lib' for a single helper, or dynamic import paths the bundler can't tracelet storing per-user/request data on the serveruseEffect(() => { fetch(...) }, []) for initial page data a Server Component could fetchuseState uses a lazy initializerThese patterns reflect widely-documented React/Next.js performance and architecture guidance. Written for this repo; not a reproduction of any third-party document.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.