React best practices
Vercel's React and Next.js performance rules, one file per rule in rules/, ranked by impact.
local-notes.md holds local overrides that win over any rule they contradict.
Steps
1. Read the stack
Check package.json and next.config.* for:
- the
reactandnextversions in use - React Compiler:
reactCompilerinnext.config.*, orbabel-plugin-react-compilerin a build config - Next.js Cache Components:
cacheComponents: trueinnext.config.*
When React Compiler is on, or the project uses Next.js, read local-notes.md before
step 2 - it says which memoisation rules the compiler makes redundant and how Next.js 16 caching
changes the server- and async- rules.
Done when you can state the React version, whether Next.js is present, and whether React Compiler and Cache Components are each on or off.
2. Read the rules for the categories in play
Pick the categories the task touches from the index below. For a review or audit, take all eight in
priority order. Open rules/<rule-id>.md for each rule you apply or check - the index line is a
summary, and the file carries the incorrect and correct code and the conditions for applying it.
Done when every rule file in the chosen categories has been read.
3. Apply or report
When writing code, apply the rules as you go. When reviewing, report each finding as: rule id, file and line, impact level, and the fix. Work from CRITICAL down, so waterfalls and bundle size come before re-render and micro-optimisations.
Done when every finding names a rule id, and every chosen category has been checked against the code.
Rule index
| Priority | Category | Impact | Prefix |
|----------|----------|--------|--------|
| 1 | Eliminating waterfalls | CRITICAL | async- |
| 2 | Bundle size | CRITICAL | bundle- |
| 3 | Server-side performance | HIGH | server- |
| 4 | Client-side data fetching | MEDIUM-HIGH | client- |
| 5 | Re-render optimisation | MEDIUM | rerender- |
| 6 | Rendering performance | MEDIUM | rendering- |
| 7 | JavaScript performance | LOW-MEDIUM | js- |
| 8 | Advanced patterns | LOW | advanced- |
1. Eliminating waterfalls (CRITICAL)
async-cheap-condition-before-await- Check cheap sync conditions before awaiting flags or remote valuesasync-defer-await- Move await into branches where actually usedasync-parallel- Use Promise.all() for independent operationsasync-dependencies- Use better-all for partial dependenciesasync-api-routes- Start promises early, await late in API routesasync-suspense-boundaries- Use Suspense to stream content
2. Bundle size (CRITICAL)
bundle-barrel-imports- Import directly, avoid barrel filesbundle-analyzable-paths- Prefer statically analysable import and file-system pathsbundle-dynamic-imports- Use next/dynamic for heavy componentsbundle-defer-third-party- Load analytics and logging after hydrationbundle-conditional- Load modules only when the feature is activatedbundle-preload- Preload on hover or focus for perceived speed
3. Server-side performance (HIGH)
server-auth-actions- Authenticate server actions like API routesserver-cache-react- Use React.cache() for per-request deduplicationserver-cache-lru- Use an LRU cache for cross-request cachingserver-dedup-props- Avoid duplicate serialisation in RSC propsserver-hoist-static-io- Hoist static I/O (fonts, logos) to module levelserver-no-shared-module-state- Avoid module-level mutable request state in RSC/SSRserver-serialization- Minimise data passed to client componentsserver-parallel-fetching- Restructure components to parallelise fetchesserver-parallel-nested-fetching- Chain nested fetches per item in Promise.allserver-after-nonblocking- Use after() for non-blocking operations
4. Client-side data fetching (MEDIUM-HIGH)
client-swr-dedup- Use SWR for automatic request deduplicationclient-event-listeners- Deduplicate global event listenersclient-passive-event-listeners- Use passive listeners for scrollclient-localstorage-schema- Version and minimise localStorage data
5. Re-render optimisation (MEDIUM)
rerender-defer-reads- Skip subscribing to state used only in callbacksrerender-memo- Extract expensive work into memoised componentsrerender-memo-with-default-value- Hoist default non-primitive propsrerender-dependencies- Use primitive dependencies in effectsrerender-derived-state- Subscribe to derived booleans, not raw valuesrerender-derived-state-no-effect- Derive state during render, not in effectsrerender-functional-setstate- Use functional setState for stable callbacksrerender-lazy-state-init- Pass a function to useState for expensive valuesrerender-simple-expression-in-memo- Leave simple primitive expressions out of useMemorerender-split-combined-hooks- Split hooks with independent dependenciesrerender-move-effect-to-event- Put interaction logic in event handlersrerender-transitions- Use startTransition for non-urgent updatesrerender-use-deferred-value- Defer expensive renders to keep input responsivererender-use-ref-transient-values- Use refs for transient, frequently changing valuesrerender-no-inline-components- Define components at module level, not inside other components
6. Rendering performance (MEDIUM)
rendering-animate-svg-wrapper- Animate a div wrapper, not the SVG elementrendering-content-visibility- Use content-visibility for long listsrendering-hoist-jsx- Extract static JSX outside componentsrendering-svg-precision- Reduce SVG coordinate precisionrendering-hydration-no-flicker- Use an inline script for client-only datarendering-hydration-suppress-warning- Suppress expected mismatchesrendering-activity- Use the Activity component for show/hiderendering-conditional-render- Use a ternary, not &&, for conditionalsrendering-usetransition-loading- Prefer useTransition for loading staterendering-resource-hints- Use React DOM resource hints for preloadingrendering-script-defer-async- Use defer or async on script tags
7. JavaScript performance (LOW-MEDIUM)
js-batch-dom-css- Group CSS changes via classes or cssTextjs-index-maps- Build a Map for repeated lookupsjs-cache-property-access- Cache object properties in loopsjs-cache-function-results- Cache function results in a module-level Mapjs-cache-storage- Cache localStorage/sessionStorage readsjs-combine-iterations- Combine multiple filter/map passes into one loopjs-length-check-first- Check array length before an expensive comparisonjs-early-exit- Return early from functionsjs-hoist-regexp- Hoist RegExp creation outside loopsjs-min-max-loop- Use a loop for min/max instead of sortjs-set-map-lookups- Use Set/Map for O(1) lookupsjs-tosorted-immutable- Use toSorted() for immutabilityjs-flatmap-filter- Use flatMap to map and filter in one passjs-request-idle-callback- Defer non-critical work to browser idle time
8. Advanced patterns (LOW)
advanced-effect-event-deps- KeepuseEffectEventresults out of effect depsadvanced-event-handler-refs- Store event handlers in refsadvanced-init-once- Initialise the app once per app loadadvanced-use-latest- useLatest for stable callback refs