# React Doctor rule prompts

429 rules grouped by category. Each rule pairs a **validation prompt** (when to confirm vs. suppress a finding) with a **fix prompt** (how to address it).

Fetch any rule as markdown at `/prompts/rules/{plugin}/{rule}.md` — for example `/prompts/rules/effect/no-derived-state.md`.

## Accessibility

- [`jsx-a11y/alt-text`](/prompts/rules/jsx-a11y/alt-text.md)
- [`jsx-a11y/anchor-is-valid`](/prompts/rules/jsx-a11y/anchor-is-valid.md)
- [`jsx-a11y/click-events-have-key-events`](/prompts/rules/jsx-a11y/click-events-have-key-events.md)
- [`jsx-a11y/heading-has-content`](/prompts/rules/jsx-a11y/heading-has-content.md)
- [`jsx-a11y/html-has-lang`](/prompts/rules/jsx-a11y/html-has-lang.md)
- [`jsx-a11y/iframe-has-title`](/prompts/rules/jsx-a11y/iframe-has-title.md)
- [`jsx-a11y/label-has-associated-control`](/prompts/rules/jsx-a11y/label-has-associated-control.md)
- [`jsx-a11y/no-autofocus`](/prompts/rules/jsx-a11y/no-autofocus.md)
- [`jsx-a11y/no-distracting-elements`](/prompts/rules/jsx-a11y/no-distracting-elements.md)
- [`jsx-a11y/no-redundant-roles`](/prompts/rules/jsx-a11y/no-redundant-roles.md)
- [`jsx-a11y/no-static-element-interactions`](/prompts/rules/jsx-a11y/no-static-element-interactions.md)
- [`jsx-a11y/role-has-required-aria-props`](/prompts/rules/jsx-a11y/role-has-required-aria-props.md)
- [`jsx-a11y/scope`](/prompts/rules/jsx-a11y/scope.md)
- [`jsx-a11y/tabindex-no-positive`](/prompts/rules/jsx-a11y/tabindex-no-positive.md)
- [`react-doctor/anchor-ambiguous-text`](/prompts/rules/react-doctor/anchor-ambiguous-text.md) — Describe a link's destination — avoid bare 'click here' / 'learn more' / 'link' as the only link text.
- [`react-doctor/anchor-has-content`](/prompts/rules/react-doctor/anchor-has-content.md) — Add visible or aria-labelled text inside every `<a>`.
- [`react-doctor/aria-activedescendant-has-tabindex`](/prompts/rules/react-doctor/aria-activedescendant-has-tabindex.md) — Add `tabIndex` to elements with `aria-activedescendant` so they're keyboard-focusable.
- [`react-doctor/aria-props`](/prompts/rules/react-doctor/aria-props.md) — Use only documented aria-* attributes from the WAI-ARIA spec.
- [`react-doctor/aria-proptypes`](/prompts/rules/react-doctor/aria-proptypes.md) — Give each aria-* attribute a value matching its WAI-ARIA type (boolean, token, integer, ID, ID-list, etc.).
- [`react-doctor/aria-role`](/prompts/rules/react-doctor/aria-role.md) — Use a documented, non-abstract WAI-ARIA role for every role attribute.
- [`react-doctor/aria-unsupported-elements`](/prompts/rules/react-doctor/aria-unsupported-elements.md) — Don't put role / aria-* attributes on reserved HTML elements like meta, head, script, or style.
- [`react-doctor/autocomplete-valid`](/prompts/rules/react-doctor/autocomplete-valid.md) — Use a valid HTML autofill token in autoComplete.
- [`react-doctor/control-has-associated-label`](/prompts/rules/react-doctor/control-has-associated-label.md) — Give every interactive control an accessible label via visible text, aria-label, aria-labelledby, or an associated label.
- [`react-doctor/design-no-vague-button-label`](/prompts/rules/react-doctor/design-no-vague-button-label.md) — Name the action: "Save changes" instead of "Continue", "Send invite" instead of "Submit", "Delete account" instead of "OK". The label IS the button's accessible name
- [`react-doctor/img-redundant-alt`](/prompts/rules/react-doctor/img-redundant-alt.md) — Drop redundant words like 'image' / 'photo' / 'picture' from alt text and describe the content instead.
- [`react-doctor/interactive-supports-focus`](/prompts/rules/react-doctor/interactive-supports-focus.md) — Add tabIndex to elements that have interactive roles and event handlers.
- [`react-doctor/lang`](/prompts/rules/react-doctor/lang.md) — Use a valid BCP-47 language tag on `<html lang>` (e.g. `en` / `en-US`).
- [`react-doctor/media-has-caption`](/prompts/rules/react-doctor/media-has-caption.md) — Add a `<track kind="captions">` child to every `<audio>` / `<video>`.
- [`react-doctor/mouse-events-have-key-events`](/prompts/rules/react-doctor/mouse-events-have-key-events.md) — Pair mouse events with their keyboard equivalents.
- [`react-doctor/no-access-key`](/prompts/rules/react-doctor/no-access-key.md) — Don't use `accessKey` — it conflicts with assistive-technology shortcuts.
- [`react-doctor/no-aria-hidden-on-focusable`](/prompts/rules/react-doctor/no-aria-hidden-on-focusable.md) — Remove `aria-hidden` from focusable elements (or remove the focusability).
- [`react-doctor/no-disabled-zoom`](/prompts/rules/react-doctor/no-disabled-zoom.md) — Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If your layout breaks at 200% zoom, fix the layout — don't punish users with disabilities
- [`react-doctor/no-gray-on-colored-background`](/prompts/rules/react-doctor/no-gray-on-colored-background.md) — Use a darker shade of the background color for text, or white/near-white for contrast. Gray text on colored backgrounds looks washed out
- [`react-doctor/no-interactive-element-to-noninteractive-role`](/prompts/rules/react-doctor/no-interactive-element-to-noninteractive-role.md) — Don't override an interactive element's semantics with a non-interactive role.
- [`react-doctor/no-justified-text`](/prompts/rules/react-doctor/no-justified-text.md) — Use `text-align: left` for body text, or add `hyphens: auto` and `overflow-wrap: break-word` if you must justify
- [`react-doctor/no-noninteractive-element-interactions`](/prompts/rules/react-doctor/no-noninteractive-element-interactions.md) — Move the interaction to a semantic interactive element, or add an interactive role plus keyboard support.
- [`react-doctor/no-noninteractive-element-to-interactive-role`](/prompts/rules/react-doctor/no-noninteractive-element-to-interactive-role.md) — Use a semantic interactive element instead of role-promoting a non-interactive one.
- [`react-doctor/no-noninteractive-tabindex`](/prompts/rules/react-doctor/no-noninteractive-tabindex.md) — Reserve tabIndex for interactive elements or interactive roles; remove it from non-interactive ones.
- [`react-doctor/no-outline-none`](/prompts/rules/react-doctor/no-outline-none.md) — Use `:focus-visible { outline: 2px solid var(--color-muted); outline-offset: 2px }` to show focus only for keyboard users while hiding it for mouse clicks
- [`react-doctor/no-tiny-text`](/prompts/rules/react-doctor/no-tiny-text.md) — Use at least 12px for body content, 16px is ideal. Small text is hard to read, especially on high-DPI mobile screens
- [`react-doctor/prefer-html-dialog`](/prompts/rules/react-doctor/prefer-html-dialog.md) — Replace the hand-rolled modal wrapper with a native <dialog> opened via dialog.showModal()
- [`react-doctor/prefer-tag-over-role`](/prompts/rules/react-doctor/prefer-tag-over-role.md) — Replace role with the semantic HTML element when one exists.
- [`react-doctor/require-reduced-motion`](/prompts/rules/react-doctor/require-reduced-motion.md) — Project ships a motion library but never gates animation on the user's reduced-motion preference — add `useReducedMotion()` / `<MotionConfig reducedMotion="user">` or a `@media (prefers-reduced-motion: reduce)` query
- [`react-doctor/role-supports-aria-props`](/prompts/rules/react-doctor/role-supports-aria-props.md) — Use only aria-* props that are supported by the element's explicit or implicit ARIA role.

## Architecture

- [`deslop/complex-function`](/prompts/rules/deslop/complex-function.md) — Flag functions exceeding configured cyclomatic/cognitive/param/line thresholds; suggest decomposing.
- [`deslop/cross-file-duplicate-export`](/prompts/rules/deslop/cross-file-duplicate-export.md) — Flag the same export name emitted from multiple files that share a common importer (ambiguous public name).
- [`deslop/duplicate-block`](/prompts/rules/deslop/duplicate-block.md) — Disallow copy-pasted code blocks duplicated across locations.
- [`deslop/duplicate-constant`](/prompts/rules/deslop/duplicate-constant.md) — Unify the same literal value duplicated as constants across 3+ files into one shared constant.
- [`deslop/duplicate-export`](/prompts/rules/deslop/duplicate-export.md) — Flag a name exported more than once from a single file, e.g. `export { x } from "./a"` plus a later `export const x`, collapsing to one canonical export.
- [`deslop/duplicate-import`](/prompts/rules/deslop/duplicate-import.md) — Merge multiple import statements from the same module specifier into one.
- [`deslop/duplicate-inline-type`](/prompts/rules/deslop/duplicate-inline-type.md) — Disallow repeating the same inline object type literal; extract a named type or interface.
- [`deslop/duplicate-type-definition`](/prompts/rules/deslop/duplicate-type-definition.md) — Flag named type aliases/interfaces with the same structural shape duplicated across files; extract one shared type.
- [`deslop/identity-wrapper`](/prompts/rules/deslop/identity-wrapper.md) — Flag a thin function that just forwards its args to another callable unchanged, e.g. const getUser = (id) => fetchUser(id).
- [`deslop/private-type-leak`](/prompts/rules/deslop/private-type-leak.md) — Disallow exporting a symbol whose signature references a locally-declared, non-exported type.
- [`deslop/redundant-alias`](/prompts/rules/deslop/redundant-alias.md) — Disallow an alias that renames a symbol to itself or round-trips with no net change, e.g. `import { X as X }`.
- [`deslop/redundant-type-pattern`](/prompts/rules/deslop/redundant-type-pattern.md) — Disallow redundant type expressions with a no-op operand, e.g. `T & {}`, `Partial<Partial<T>>`, `Pick<X, keyof X>`.
- [`deslop/shadowed-directory`](/prompts/rules/deslop/shadowed-directory.md) — Flag two directories that look like a forked/copied tree (e.g. src/ vs deno/lib/) sharing many duplicated same-named files.
- [`react-doctor/design-no-bold-heading`](/prompts/rules/react-doctor/design-no-bold-heading.md) — Use `font-semibold` (600) or `font-medium` (500) on headings — 700+ crushes letter counter shapes at display sizes
- [`react-doctor/design-no-default-tailwind-palette`](/prompts/rules/react-doctor/design-no-default-tailwind-palette.md) — Replace `indigo-*` / `gray-*` / `slate-*` with project tokens, your brand color, or a less-default neutral (`zinc`, `neutral`, `stone`)
- [`react-doctor/design-no-em-dash-in-jsx-text`](/prompts/rules/react-doctor/design-no-em-dash-in-jsx-text.md) — Replace em dashes in JSX prose with commas, colons, semicolons, or parentheses so UI copy reads less like generated text.
- [`react-doctor/design-no-redundant-padding-axes`](/prompts/rules/react-doctor/design-no-redundant-padding-axes.md) — Collapse `px-N py-N` to `p-N` when both axes match. Keep them split only when one axis varies at a breakpoint (`py-2 md:py-3`)
- [`react-doctor/design-no-redundant-size-axes`](/prompts/rules/react-doctor/design-no-redundant-size-axes.md) — Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes match
- [`react-doctor/design-no-space-on-flex-children`](/prompts/rules/react-doctor/design-no-space-on-flex-children.md) — Use `gap-*` on the flex/grid parent. `space-x-*` / `space-y-*` produce phantom gaps when a sibling is conditionally rendered, lose vertical spacing on wrapped lines, and don't mirror in RTL
- [`react-doctor/design-no-three-period-ellipsis`](/prompts/rules/react-doctor/design-no-three-period-ellipsis.md) — Use the typographic ellipsis "…" (or `&hellip;`) instead of three periods — pairs with action-with-followup labels ("Rename…", "Loading…")
- [`react-doctor/display-name`](/prompts/rules/react-doctor/display-name.md) — Give each component a stable displayName so React DevTools shows a real name instead of "Unknown".
- [`react-doctor/forbid-component-props`](/prompts/rules/react-doctor/forbid-component-props.md) — Configure forbidden props per component via the `forbidComponentProps.forbid` setting.
- [`react-doctor/forbid-dom-props`](/prompts/rules/react-doctor/forbid-dom-props.md) — Configure forbidden DOM props via the `forbidDomProps.forbid` setting to keep disallowed attributes off DOM nodes.
- [`react-doctor/forbid-elements`](/prompts/rules/react-doctor/forbid-elements.md) — Replace each configured forbidden element with its sanctioned component or wrapper.
- [`react-doctor/forward-ref-uses-ref`](/prompts/rules/react-doctor/forward-ref-uses-ref.md) — Either accept a `ref` parameter in the forwardRef render function, or drop the forwardRef wrapper entirely.
- [`react-doctor/hook-use-state`](/prompts/rules/react-doctor/hook-use-state.md) — Destructure useState as `const [thing, setThing] = useState(…)`.
- [`react-doctor/jsx-boolean-value`](/prompts/rules/react-doctor/jsx-boolean-value.md) — Pick one boolean-attribute style codebase-wide (default: omit `={true}`, e.g. write `<C foo />`).
- [`react-doctor/jsx-curly-brace-presence`](/prompts/rules/react-doctor/jsx-curly-brace-presence.md) — Pick a consistent quoting style for JSX literal values and drop redundant curly braces around plain strings.
- [`react-doctor/jsx-filename-extension`](/prompts/rules/react-doctor/jsx-filename-extension.md) — Use .jsx / .tsx (or your project's chosen extension) for files containing JSX.
- [`react-doctor/jsx-fragments`](/prompts/rules/react-doctor/jsx-fragments.md) — Pick one fragment style across the codebase — use the <></> shorthand by default.
- [`react-doctor/jsx-handler-names`](/prompts/rules/react-doctor/jsx-handler-names.md) — Use the `on…` prefix for event-handler props and `handle…` for the functions that handle them.
- [`react-doctor/jsx-max-depth`](/prompts/rules/react-doctor/jsx-max-depth.md) — Extract deeply nested JSX into smaller components to keep render trees readable.
- [`react-doctor/jsx-no-useless-fragment`](/prompts/rules/react-doctor/jsx-no-useless-fragment.md) — Drop the fragment when it wraps a single child or holds multiple children directly under an HTML tag.
- [`react-doctor/jsx-pascal-case`](/prompts/rules/react-doctor/jsx-pascal-case.md) — Rename custom JSX components to PascalCase.
- [`react-doctor/jsx-props-no-spreading`](/prompts/rules/react-doctor/jsx-props-no-spreading.md) — List each prop explicitly so consumers can see what's being passed instead of spreading.
- [`react-doctor/no-clone-element`](/prompts/rules/react-doctor/no-clone-element.md) — Pass children, render props, or Children.map instead of cloning elements with React.cloneElement.
- [`react-doctor/no-dark-mode-glow`](/prompts/rules/react-doctor/no-dark-mode-glow.md) — Use a subtle `box-shadow` with neutral colors for depth, or `border` with low opacity. Colored glows on dark backgrounds are the default AI-generated aesthetic
- [`react-doctor/no-default-props`](/prompts/rules/react-doctor/no-default-props.md) — React 19 removes `Component.defaultProps` for function components. Move the defaults into the destructured props parameter: `function Foo({ size = "md", variant = "primary" })` instead of `Foo.defaultProps = { size: "md", variant: "primary" }`.
- [`react-doctor/no-generic-handler-names`](/prompts/rules/react-doctor/no-generic-handler-names.md) — Rename to describe the action: e.g. `handleSubmit` → `saveUserProfile`, `handleClick` → `toggleSidebar`
- [`react-doctor/no-giant-component`](/prompts/rules/react-doctor/no-giant-component.md) — Extract logical sections into focused components: `<UserHeader />`, `<UserActions />`, etc.
- [`react-doctor/no-gradient-text`](/prompts/rules/react-doctor/no-gradient-text.md) — Use solid text colors for readability. If you need emphasis, use font weight, size, or a distinct color instead of gradients
- [`react-doctor/no-inline-exhaustive-style`](/prompts/rules/react-doctor/no-inline-exhaustive-style.md) — Move styles to a CSS class, CSS module, Tailwind utilities, or a styled component — inline objects with many properties hurt readability and create new references every render
- [`react-doctor/no-many-boolean-props`](/prompts/rules/react-doctor/no-many-boolean-props.md) — Split into compound components or named variants: `<Button.Primary />`, `<DialogConfirm />` instead of stacking `isPrimary`, `isConfirm` flags
- [`react-doctor/no-multi-comp`](/prompts/rules/react-doctor/no-multi-comp.md) — Move secondary components into their own files.
- [`react-doctor/no-polymorphic-children`](/prompts/rules/react-doctor/no-polymorphic-children.md) — Expose explicit subcomponents (`<Button.Text>`, `<Button.Icon>`) so consumers don't need to switch on `typeof children`
- [`react-doctor/no-prop-types`](/prompts/rules/react-doctor/no-prop-types.md) — Move propTypes to TypeScript types: `type Props = { value: number }; function Component(props: Props)` — React 19 ignores runtime propTypes
- [`react-doctor/no-pure-black-background`](/prompts/rules/react-doctor/no-pure-black-background.md) — Tint the background slightly toward your brand hue — e.g. `#0a0a0f` or Tailwind's `bg-gray-950`. Pure black looks harsh on modern displays
- [`react-doctor/no-react-children`](/prompts/rules/react-doctor/no-react-children.md) — Pass children as props or render them directly instead of calling React.Children methods.
- [`react-doctor/no-react-dom-deprecated-apis`](/prompts/rules/react-doctor/no-react-dom-deprecated-apis.md) — Switch the legacy `react-dom` root API (`render` / `hydrate` / `unmountComponentAtNode`) to `createRoot` / `hydrateRoot` / `root.unmount()` from `react-dom/client`. Replace `findDOMNode` with a ref. The whole `react-dom/test-utils` entry point is removed in React 19 — use `act` from `react` and `fireEvent` / `render` from `@testing-library/react`. Only enabled on projects detected as React 18+.
- [`react-doctor/no-react19-deprecated-apis`](/prompts/rules/react-doctor/no-react19-deprecated-apis.md) — Pass `ref` as a regular prop on function components — `forwardRef` is no longer needed in React 19+. Replace `useContext(X)` with `use(X)` for branch-aware context reads. Only enabled on projects detected as React 19+.
- [`react-doctor/no-redundant-should-component-update`](/prompts/rules/react-doctor/no-redundant-should-component-update.md) — Drop shouldComponentUpdate when extending PureComponent, or extend React.Component if custom comparison logic is genuinely needed.
- [`react-doctor/no-render-in-render`](/prompts/rules/react-doctor/no-render-in-render.md) — Extract to a named component: `const ListItem = ({ item }) => <div>{item.name}</div>`
- [`react-doctor/no-render-prop-children`](/prompts/rules/react-doctor/no-render-prop-children.md) — Replace `renderXxx` props with compound subcomponents (e.g. `<Modal.Header>`) or `children` so the parent doesn't dictate every customization point
- [`react-doctor/no-set-state`](/prompts/rules/react-doctor/no-set-state.md) — Lift state up or use an external store instead of this.setState.
- [`react-doctor/no-side-tab-border`](/prompts/rules/react-doctor/no-side-tab-border.md) — Use a subtler accent (box-shadow inset, background gradient, or border-bottom) instead of a thick one-sided border
- [`react-doctor/no-unescaped-entities`](/prompts/rules/react-doctor/no-unescaped-entities.md) — Replace bare ' / " / > / } characters in JSX text with HTML entities.
- [`react-doctor/no-wide-letter-spacing`](/prompts/rules/react-doctor/no-wide-letter-spacing.md) — Reserve wide tracking (letter-spacing > 0.05em) for short uppercase labels, navigation items, and buttons — not body text
- [`react-doctor/no-z-index-9999`](/prompts/rules/react-doctor/no-z-index-9999.md) — Define a z-index scale in your design tokens (e.g. dropdown: 10, modal: 20, toast: 30). Create a new stacking context with `isolation: isolate` instead of escalating values
- [`react-doctor/only-export-components`](/prompts/rules/react-doctor/only-export-components.md) — Move non-component exports out of files that export components.
- [`react-doctor/prefer-es6-class`](/prompts/rules/react-doctor/prefer-es6-class.md) — Use one component style consistently — ES2015 `class extends React.Component` (default) over the legacy `createReactClass` factory.
- [`react-doctor/prefer-explicit-variants`](/prompts/rules/react-doctor/prefer-explicit-variants.md) — Split into explicit variant components: render `<ThreadComposer />` and `<EditMessageComposer />` instead of one component switching subtrees on boolean props.
- [`react-doctor/prefer-function-component`](/prompts/rules/react-doctor/prefer-function-component.md) — Re-write the class component as a function component using hooks.
- [`react-doctor/prefer-module-scope-pure-function`](/prompts/rules/react-doctor/prefer-module-scope-pure-function.md) — Hoist the pure helper to module scope (above the component) so it isn't reallocated each render: const formatName = (user) => ...
- [`react-doctor/prefer-module-scope-static-value`](/prompts/rules/react-doctor/prefer-module-scope-static-value.md) — Hoist the static array/object literal to module scope above the component: const FILTER_OPTIONS = ["all", "active", "done"]; function App() { ... }
- [`react-doctor/react-compiler-destructure-method`](/prompts/rules/react-doctor/react-compiler-destructure-method.md) — Destructure the method up front: `const { push } = useRouter()` then call `push(...)` directly — clearer dependency graph and easier for React Compiler to memoize
- [`react-doctor/react-compiler-no-manual-memoization`](/prompts/rules/react-doctor/react-compiler-no-manual-memoization.md) — Delete the React useMemo / useCallback / memo wrapper — React Compiler memoizes it for you.
- [`react-doctor/self-closing-comp`](/prompts/rules/react-doctor/self-closing-comp.md) — Use the self-closing form `<X />` for elements with no children.
- [`react-doctor/state-in-constructor`](/prompts/rules/react-doctor/state-in-constructor.md) — Pick one state-initialization style for class components — class field or constructor — and use it consistently.
- [`react-doctor/zod-v4-no-deprecated-error-apis`](/prompts/rules/react-doctor/zod-v4-no-deprecated-error-apis.md) — Replace deprecated ZodError helpers with Zod 4 functions: z.treeifyError(), z.flattenError(), z.prettifyError(), or read error.issues directly
- [`react-doctor/zod-v4-no-deprecated-error-customization`](/prompts/rules/react-doctor/zod-v4-no-deprecated-error-customization.md) — Replace deprecated Zod error customization with the v4 unified { error } API: z.string({ error: "Required" })
- [`react-doctor/zod-v4-no-deprecated-schema-apis`](/prompts/rules/react-doctor/zod-v4-no-deprecated-schema-apis.md) — Migrate deprecated Zod 4 schema APIs: z.object().strict() to z.strictObject(), z.nativeEnum to z.enum, z.record(value) to z.record(key, value), z.function().args().returns() to z.function({ input, output }).
- [`react-doctor/zod-v4-prefer-top-level-string-formats`](/prompts/rules/react-doctor/zod-v4-prefer-top-level-string-formats.md) — Replace z.string().<format>() with the Zod 4 top-level format API, e.g. z.email() or z.uuid()

## Bundle Size

- [`react-doctor/no-barrel-import`](/prompts/rules/react-doctor/no-barrel-import.md) — Import from the direct path: `import { Button } from './components/Button'` instead of `./components`
- [`react-doctor/no-dynamic-import-path`](/prompts/rules/react-doctor/no-dynamic-import-path.md) — Use a string-literal path: `import('./feature/heavy.js')` so the bundler can split this chunk
- [`react-doctor/no-full-lodash-import`](/prompts/rules/react-doctor/no-full-lodash-import.md) — Import the specific function: `import debounce from 'lodash/debounce'` — saves ~70kb
- [`react-doctor/no-moment`](/prompts/rules/react-doctor/no-moment.md) — Replace with `import { format } from 'date-fns'` (tree-shakeable) or `import dayjs from 'dayjs'` (2kb)
- [`react-doctor/no-undeferred-third-party`](/prompts/rules/react-doctor/no-undeferred-third-party.md) — Use `next/script` with `strategy="lazyOnload"` or add the `defer` attribute
- [`react-doctor/prefer-dynamic-import`](/prompts/rules/react-doctor/prefer-dynamic-import.md) — Use `const Component = dynamic(() => import('library'), { ssr: false })` from next/dynamic or React.lazy()
- [`react-doctor/use-lazy-motion`](/prompts/rules/react-doctor/use-lazy-motion.md) — Use `import { LazyMotion, m } from "framer-motion"` with `domAnimation` features — saves ~30kb

## Correctness

- [`deslop/commonjs-in-esm`](/prompts/rules/deslop/commonjs-in-esm.md) — Flag CommonJS constructs (require/module.exports/exports.x) inside an ESM module.
- [`deslop/lazy-import-at-top-level`](/prompts/rules/deslop/lazy-import-at-top-level.md) — Flag a dynamic import() at module top level that is awaited or .then/.catch/.finally-ed during load (no laziness benefit); prefer a static import.
- [`deslop/simplifiable-expression`](/prompts/rules/deslop/simplifiable-expression.md) — Disallow expressions that collapse to a simpler equivalent, e.g. !!x → Boolean(x).
- [`deslop/simplifiable-function`](/prompts/rules/deslop/simplifiable-function.md) — Disallow functions written less directly than needed: block-arrow-single-return, redundant-await-return, useless-async-no-await.
- [`deslop/ts-escape-hatch`](/prompts/rules/deslop/ts-escape-hatch.md) — Disallow TypeScript suppressions that hide type errors.
- [`deslop/unnecessary-assertion`](/prompts/rules/deslop/unnecessary-assertion.md) — Flag TypeScript assertions that are no-ops or weaken types (`x!!`, `as any`, `as unknown as T`, `<T>x`).
- [`react-doctor/button-has-type`](/prompts/rules/react-doctor/button-has-type.md) — Set type="button" (or "submit"/"reset") explicitly on every <button> so it never defaults to submit.
- [`react-doctor/checked-requires-onchange-or-readonly`](/prompts/rules/react-doctor/checked-requires-onchange-or-readonly.md) — Pair `checked` with `onChange={…}` (controlled) or `readOnly` (display-only), and never combine `checked` with `defaultChecked`.
- [`react-doctor/client-localstorage-no-version`](/prompts/rules/react-doctor/client-localstorage-no-version.md) — Bake a version into the storage key (e.g. "myKey:v1"); a future schema change can ignore old data instead of crashing on it
- [`react-doctor/exhaustive-deps`](/prompts/rules/react-doctor/exhaustive-deps.md) — Match the deps array to what the hook callback actually captures, or stabilize/move recreated values instead of blindly adding them.
- [`react-doctor/html-no-invalid-paragraph-child`](/prompts/rules/react-doctor/html-no-invalid-paragraph-child.md) — Replace the wrapping <p> with a <div>, or hoist the block-level child out of the paragraph
- [`react-doctor/html-no-invalid-table-nesting`](/prompts/rules/react-doctor/html-no-invalid-table-nesting.md) — Author each table element under its required host parent: thead/tbody/tfoot in <table>, tr in a row group, td/th in <tr>
- [`react-doctor/html-no-nested-interactive`](/prompts/rules/react-doctor/html-no-nested-interactive.md) — Hoist the inner interactive element out, or make the outer one a non-interactive wrapper: change <a><a/></a> to <a/> next to <a/>, or wrap with <div>/<span>
- [`react-doctor/jsx-no-comment-textnodes`](/prompts/rules/react-doctor/jsx-no-comment-textnodes.md) — Wrap JSX comments in `{/* … */}` so they're parsed as comments, not rendered as literal child text.
- [`react-doctor/jsx-no-undef`](/prompts/rules/react-doctor/jsx-no-undef.md) — Import the component or fix the typo so the JSX element name resolves to a real binding.
- [`react-doctor/jsx-props-no-spread-multi`](/prompts/rules/react-doctor/jsx-props-no-spread-multi.md) — Spread each unique expression at most once per JSX element.
- [`react-doctor/no-array-index-as-key`](/prompts/rules/react-doctor/no-array-index-as-key.md) — Use a stable unique identifier: `key={item.id}` or `key={item.slug}` — index keys break on reorder/filter
- [`react-doctor/no-create-context-in-render`](/prompts/rules/react-doctor/no-create-context-in-render.md) — Move createContext to module scope so its Context identity stays stable across renders
- [`react-doctor/no-create-store-in-render`](/prompts/rules/react-doctor/no-create-store-in-render.md) — Hoist the store/atom/observable construction to module scope: const store = create(...) outside the component.
- [`react-doctor/no-danger-with-children`](/prompts/rules/react-doctor/no-danger-with-children.md) — Use either children or dangerouslySetInnerHTML on an element, never both.
- [`react-doctor/no-document-start-view-transition`](/prompts/rules/react-doctor/no-document-start-view-transition.md) — Render a <ViewTransition> component and update inside startTransition / useDeferredValue — React calls startViewTransition for you
- [`react-doctor/no-find-dom-node`](/prompts/rules/react-doctor/no-find-dom-node.md) — Use refs (useRef/createRef) to access DOM nodes instead of the removed findDOMNode API.
- [`react-doctor/no-jsx-element-type`](/prompts/rules/react-doctor/no-jsx-element-type.md) — Widen the return type from JSX.Element to React.ReactNode: function App(): React.ReactNode
- [`react-doctor/no-legacy-class-lifecycles`](/prompts/rules/react-doctor/no-legacy-class-lifecycles.md) — Move side effects in `componentWillMount` to `componentDidMount`; replace `componentWillReceiveProps` with `componentDidUpdate` (compare prevProps) or the static `getDerivedStateFromProps` for pure state derivation; replace `componentWillUpdate` with `getSnapshotBeforeUpdate` paired with `componentDidUpdate`. The `UNSAFE_` prefix only silences the warning — React 19 removes both forms.
- [`react-doctor/no-legacy-context-api`](/prompts/rules/react-doctor/no-legacy-context-api.md) — Replace `childContextTypes` + `getChildContext` with `const MyContext = createContext(...)` + `<MyContext.Provider value={...}>`; replace `contextTypes` with `static contextType = MyContext` (single context) or `useContext()` / `use()` from a function component. The provider and every consumer must migrate together — partial migrations leave consumers reading the wrong context.
- [`react-doctor/no-namespace`](/prompts/rules/react-doctor/no-namespace.md) — Drop the namespace and use a plain (Pascal-cased) component or DOM tag.
- [`react-doctor/no-nested-component-definition`](/prompts/rules/react-doctor/no-nested-component-definition.md) — Move to a separate file or to module scope above the parent component
- [`react-doctor/no-prevent-default`](/prompts/rules/react-doctor/no-prevent-default.md) — Use `<form action={serverAction}>` (works without JS) or `<button>` instead of `<a>` with preventDefault
- [`react-doctor/no-random-key`](/prompts/rules/react-doctor/no-random-key.md) — Replace the fresh-each-render key with a stable id from the item: key={item.id}
- [`react-doctor/no-this-in-sfc`](/prompts/rules/react-doctor/no-this-in-sfc.md) — Use the function's `props` parameter instead of `this.props` in stateless function components.
- [`react-doctor/no-uncontrolled-input`](/prompts/rules/react-doctor/no-uncontrolled-input.md) — Pass an explicit initial value to `useState` (e.g. `useState("")` instead of `useState()`), add `onChange` (or `readOnly` to opt out) when you supply `value`, and drop `defaultValue` on controlled inputs — React ignores it
- [`react-doctor/no-unsafe`](/prompts/rules/react-doctor/no-unsafe.md) — Replace UNSAFE_componentWillMount / WillReceiveProps / WillUpdate with their modern lifecycle equivalents.
- [`react-doctor/react-in-jsx-scope`](/prompts/rules/react-doctor/react-in-jsx-scope.md) — If on React 17+ with the new JSX transform, disable this rule; otherwise import React at the top of the file.
- [`react-doctor/rendering-conditional-render`](/prompts/rules/react-doctor/rendering-conditional-render.md) — Change to `{items.length > 0 && <List />}` or use a ternary: `{items.length ? <List /> : null}`
- [`react-doctor/rendering-hydration-mismatch-time`](/prompts/rules/react-doctor/rendering-hydration-mismatch-time.md) — Wrap dynamic time/random values in useEffect+useState (client-only) or add suppressHydrationWarning to the parent if intentional
- [`react-doctor/style-prop-object`](/prompts/rules/react-doctor/style-prop-object.md) — Pass the `style` prop as an object literal like `{{ color: 'red' }}`, never a string or other primitive.
- [`react-doctor/void-dom-elements-no-children`](/prompts/rules/react-doctor/void-dom-elements-no-children.md) — Remove children from the void element, or use a non-void element if children are needed.
- [`react/jsx-key`](/prompts/rules/react/jsx-key.md)
- [`react/jsx-no-duplicate-props`](/prompts/rules/react/jsx-no-duplicate-props.md)
- [`react/jsx-no-script-url`](/prompts/rules/react/jsx-no-script-url.md)
- [`react/no-children-prop`](/prompts/rules/react/no-children-prop.md)
- [`react/no-danger`](/prompts/rules/react/no-danger.md)
- [`react/no-direct-mutation-state`](/prompts/rules/react/no-direct-mutation-state.md)
- [`react/no-is-mounted`](/prompts/rules/react/no-is-mounted.md)
- [`react/no-render-return-value`](/prompts/rules/react/no-render-return-value.md)
- [`react/no-string-refs`](/prompts/rules/react/no-string-refs.md)
- [`react/no-unknown-property`](/prompts/rules/react/no-unknown-property.md)
- [`react/require-render-return`](/prompts/rules/react/require-render-return.md)
- [`react/rules-of-hooks`](/prompts/rules/react/rules-of-hooks.md)

## Dead Code

- [`deslop/circular-dependency`](/prompts/rules/deslop/circular-dependency.md) — Disallow runtime import cycles between modules (A imports B ... imports A).
- [`deslop/feature-flag`](/prompts/rules/deslop/feature-flag.md) — Disallow stale feature flags that permanently guard dead code.
- [`deslop/misclassified-dependency`](/prompts/rules/deslop/misclassified-dependency.md) — Move a runtime dep consumed only via `import type` into devDependencies.
- [`deslop/re-export-cycle`](/prompts/rules/deslop/re-export-cycle.md) — Disallow import cycles formed by re-export statements (export * / export { } from).
- [`deslop/unused-class-member`](/prompts/rules/deslop/unused-class-member.md) — Flag a public/protected class method, property, or accessor that is declared but never referenced anywhere in the codebase.
- [`deslop/unused-dependency`](/prompts/rules/deslop/unused-dependency.md) — Flag a package.json dependencies entry that no scanned source file imports as an unused dependency (deslop detectStalePackages, UnusedDependency name/isDevDependency=false).
- [`deslop/unused-dev-dependency`](/prompts/rules/deslop/unused-dev-dependency.md) — Flag a devDependencies entry (isDevDependency=true) never imported by any scanned source file.
- [`deslop/unused-enum-member`](/prompts/rules/deslop/unused-enum-member.md) — Flag an enum member that is declared but never referenced anywhere in the project (e.g. `enum Color { Red, Green, Blue }` where `Color.Blue` is never used).
- [`deslop/unused-export`](/prompts/rules/deslop/unused-export.md) — Flag a named or default value export (isTypeOnly=false) that no other module in the project imports.
- [`deslop/unused-file`](/prompts/rules/deslop/unused-file.md) — Flag a source file unreachable from any configured entry point (deslop unusedFiles / detectOrphanFiles), likely dead — delete it.
- [`deslop/unused-type`](/prompts/rules/deslop/unused-type.md) — Disallow exporting a type/interface that no other module imports (opt-in, requires reportTypes).
- [`deslop/unused-type-declaration`](/prompts/rules/deslop/unused-type-declaration.md) — Disallow exported type declarations (interface / type-alias / enum-type) that are never referenced anywhere in the project.

## Next.js

- [`react-doctor/nextjs-async-client-component`](/prompts/rules/react-doctor/nextjs-async-client-component.md) — Fetch data in a parent Server Component and pass it as props, or use useQuery/useSWR in the client component
- [`react-doctor/nextjs-error-boundary-missing-use-client`](/prompts/rules/react-doctor/nextjs-error-boundary-missing-use-client.md) — Add `'use client'` as the first statement of error.tsx / global-error.tsx so the error boundary becomes a Client Component.
- [`react-doctor/nextjs-global-error-missing-html-body`](/prompts/rules/react-doctor/nextjs-global-error-missing-html-body.md) — Wrap the global-error UI in `<html><body>...</body></html>` because the root layout unmounts when global-error renders
- [`react-doctor/nextjs-image-missing-sizes`](/prompts/rules/react-doctor/nextjs-image-missing-sizes.md) — Add sizes for responsive behavior: `sizes="(max-width: 768px) 100vw, 50vw"` matching your layout breakpoints
- [`react-doctor/nextjs-inline-script-missing-id`](/prompts/rules/react-doctor/nextjs-inline-script-missing-id.md) — Add `id="descriptive-name"` so Next.js can track, deduplicate, and re-execute the script correctly
- [`react-doctor/nextjs-missing-metadata`](/prompts/rules/react-doctor/nextjs-missing-metadata.md) — Add `export const metadata = { title: '...', description: '...' }` or `export async function generateMetadata()`
- [`react-doctor/nextjs-no-a-element`](/prompts/rules/react-doctor/nextjs-no-a-element.md) — `import Link from 'next/link'` — enables client-side navigation, prefetching, and preserves scroll position
- [`react-doctor/nextjs-no-client-fetch-for-server-data`](/prompts/rules/react-doctor/nextjs-no-client-fetch-for-server-data.md) — Remove 'use client' and fetch directly in the Server Component — no API round-trip, secrets stay on server
- [`react-doctor/nextjs-no-client-side-redirect`](/prompts/rules/react-doctor/nextjs-no-client-side-redirect.md) — Avoid redirects inside useEffect. Use an event handler, middleware, or server-side redirect (App Router: redirect() from next/navigation; Pages Router: getServerSideProps redirect)
- [`react-doctor/nextjs-no-css-link`](/prompts/rules/react-doctor/nextjs-no-css-link.md) — Import CSS directly: `import './styles.css'` or use CSS Modules: `import styles from './Button.module.css'`
- [`react-doctor/nextjs-no-default-export-in-route-handler`](/prompts/rules/react-doctor/nextjs-no-default-export-in-route-handler.md) — Remove the default export from this route.ts and export the handler as a named HTTP method instead: export async function GET(request: Request) { ... }
- [`react-doctor/nextjs-no-edge-og-runtime`](/prompts/rules/react-doctor/nextjs-no-edge-og-runtime.md) — Remove `export const runtime = "edge"` from OG image route files so they use the default Node.js runtime
- [`react-doctor/nextjs-no-font-link`](/prompts/rules/react-doctor/nextjs-no-font-link.md) — `import { Inter } from "next/font/google"` — self-hosted, zero layout shift, no render-blocking requests
- [`react-doctor/nextjs-no-google-analytics-script`](/prompts/rules/react-doctor/nextjs-no-google-analytics-script.md) — Replace manual GA script with the optimized component: import { GoogleAnalytics } from '@next/third-parties/google'
- [`react-doctor/nextjs-no-head-import`](/prompts/rules/react-doctor/nextjs-no-head-import.md) — Use the Metadata API instead: `export const metadata = { title: '...' }` or `export async function generateMetadata()`
- [`react-doctor/nextjs-no-img-element`](/prompts/rules/react-doctor/nextjs-no-img-element.md) — `import Image from 'next/image'` — provides automatic WebP/AVIF, lazy loading, and responsive srcset
- [`react-doctor/nextjs-no-native-script`](/prompts/rules/react-doctor/nextjs-no-native-script.md) — `import Script from "next/script"` — use `strategy="afterInteractive"` for analytics or `"lazyOnload"` for widgets
- [`react-doctor/nextjs-no-polyfill-script`](/prompts/rules/react-doctor/nextjs-no-polyfill-script.md) — Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automatically
- [`react-doctor/nextjs-no-redirect-in-try-catch`](/prompts/rules/react-doctor/nextjs-no-redirect-in-try-catch.md) — Move the redirect/notFound call outside the try block, or add `unstable_rethrow(error)` in the catch
- [`react-doctor/nextjs-no-script-in-head`](/prompts/rules/react-doctor/nextjs-no-script-in-head.md) — Move <Script> out of <Head>: next/script ignores head placement, so a Script nested in Head silently never loads
- [`react-doctor/nextjs-no-use-search-params-without-suspense`](/prompts/rules/react-doctor/nextjs-no-use-search-params-without-suspense.md) — Wrap the component using useSearchParams: `<Suspense fallback={<Skeleton />}><SearchComponent /></Suspense>`
- [`react-doctor/nextjs-no-vercel-og-import`](/prompts/rules/react-doctor/nextjs-no-vercel-og-import.md) — Replace the @vercel/og import with import { ImageResponse } from "next/og"

## Performance

- [`react-doctor/advanced-event-handler-refs`](/prompts/rules/react-doctor/advanced-event-handler-refs.md) — Store the handler in a ref and have the listener read `handlerRef.current()` — the subscription stays put while the latest handler is always called
- [`react-doctor/async-await-in-loop`](/prompts/rules/react-doctor/async-await-in-loop.md) — Collect the items and use `await Promise.all(items.map(...))` to run independent operations concurrently
- [`react-doctor/async-defer-await`](/prompts/rules/react-doctor/async-defer-await.md) — Move the `await` after the synchronous early-return guard so the skip path stays fast
- [`react-doctor/async-parallel`](/prompts/rules/react-doctor/async-parallel.md) — Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` to run independent operations concurrently
- [`react-doctor/client-passive-event-listeners`](/prompts/rules/react-doctor/client-passive-event-listeners.md) — Add `{ passive: true }` as the third argument: `addEventListener('scroll', handler, { passive: true })`. Only do this if the handler does NOT call `event.preventDefault()` — passive listeners silently ignore `preventDefault()`, which breaks features like pull-to-refresh suppression, custom gestures, and nested-scroll containment.
- [`react-doctor/js-async-reduce-without-awaited-acc`](/prompts/rules/react-doctor/js-async-reduce-without-awaited-acc.md) — Await the accumulator inside an async .reduce reducer: const acc = await previous; ...; return acc;
- [`react-doctor/js-batch-dom-css`](/prompts/rules/react-doctor/js-batch-dom-css.md) — Batch DOM/CSS reads and writes — interleaving them inside a loop causes layout thrashing. Read first, then write
- [`react-doctor/js-cache-property-access`](/prompts/rules/react-doctor/js-cache-property-access.md) — Hoist the deep member access into a const at the top of the loop body: `const { x, y } = obj.deeply.nested`
- [`react-doctor/js-cache-storage`](/prompts/rules/react-doctor/js-cache-storage.md) — Cache repeated `localStorage`/`sessionStorage` reads in a local variable — each access serializes/deserializes
- [`react-doctor/js-combine-iterations`](/prompts/rules/react-doctor/js-combine-iterations.md) — Combine `.map().filter()` (or similar chains) into a single pass with `.reduce()` or a `for...of` loop to avoid iterating the array twice
- [`react-doctor/js-early-exit`](/prompts/rules/react-doctor/js-early-exit.md) — Add an early `return` / `continue` to flatten deep nesting and short-circuit when the predicate is already known
- [`react-doctor/js-flatmap-filter`](/prompts/rules/react-doctor/js-flatmap-filter.md) — Use `.flatMap(item => condition ? [value] : [])` — transforms and filters in a single pass instead of creating an intermediate array
- [`react-doctor/js-hoist-intl`](/prompts/rules/react-doctor/js-hoist-intl.md) — Hoist `new Intl.NumberFormat(...)` to module scope or wrap in `useMemo` — Intl constructors allocate dozens of objects per locale lookup
- [`react-doctor/js-hoist-regexp`](/prompts/rules/react-doctor/js-hoist-regexp.md) — Hoist `new RegExp(...)` (or large regex literals) to a module-level constant so it isn't recompiled on every loop iteration
- [`react-doctor/js-index-maps`](/prompts/rules/react-doctor/js-index-maps.md) — Build an index `Map` once outside the loop instead of `array.find(...)` inside it
- [`react-doctor/js-length-check-first`](/prompts/rules/react-doctor/js-length-check-first.md) — Short-circuit with `a.length === b.length && a.every((x, i) => x === b[i])` — unequal-length arrays exit immediately
- [`react-doctor/js-min-max-loop`](/prompts/rules/react-doctor/js-min-max-loop.md) — Use `Math.min(...array)` / `Math.max(...array)` instead of sorting just to read the first or last element
- [`react-doctor/js-set-map-lookups`](/prompts/rules/react-doctor/js-set-map-lookups.md) — Use a `Set` or `Map` for repeated membership tests / keyed lookups — `Array.includes`/`find` is O(n) per call
- [`react-doctor/js-tosorted-immutable`](/prompts/rules/react-doctor/js-tosorted-immutable.md) — Use `array.toSorted()` (ES2023) instead of `[...array].sort()` for immutable sorting without the spread allocation
- [`react-doctor/jsx-no-constructed-context-values`](/prompts/rules/react-doctor/jsx-no-constructed-context-values.md) — Memoize the context value with useMemo/useCallback or hoist it outside the render
- [`react-doctor/jsx-no-jsx-as-prop`](/prompts/rules/react-doctor/jsx-no-jsx-as-prop.md) — Hoist the inline JSX out of render or memoize it with useMemo so the prop value is stable across renders.
- [`react-doctor/jsx-no-new-array-as-prop`](/prompts/rules/react-doctor/jsx-no-new-array-as-prop.md) — Memoize the array (useMemo) or hoist it outside the component instead of allocating a new one each render.
- [`react-doctor/jsx-no-new-function-as-prop`](/prompts/rules/react-doctor/jsx-no-new-function-as-prop.md) — Memoize the callback (useCallback) or hoist it outside the component to keep a stable reference across renders.
- [`react-doctor/jsx-no-new-object-as-prop`](/prompts/rules/react-doctor/jsx-no-new-object-as-prop.md) — Memoize the object (useMemo) or hoist it outside the component.
- [`react-doctor/no-array-index-key`](/prompts/rules/react-doctor/no-array-index-key.md) — Use a stable, data-derived key instead of the array iteration index.
- [`react-doctor/no-flush-sync`](/prompts/rules/react-doctor/no-flush-sync.md) — Use startTransition for non-urgent updates — flushSync forces a sync flush that skips View Transitions and concurrent rendering
- [`react-doctor/no-global-css-variable-animation`](/prompts/rules/react-doctor/no-global-css-variable-animation.md) — Set the variable on the nearest element instead of a parent, or use `@property` with `inherits: false` to prevent cascade. Better yet, use targeted `element.style.transform` updates
- [`react-doctor/no-inline-bounce-easing`](/prompts/rules/react-doctor/no-inline-bounce-easing.md) — Use `cubic-bezier(0.16, 1, 0.3, 1)` (ease-out-expo) for natural deceleration — objects in the real world don't bounce
- [`react-doctor/no-inline-prop-on-memo-component`](/prompts/rules/react-doctor/no-inline-prop-on-memo-component.md) — Hoist the inline `() => ...` / `[]` / `{}` to a stable reference (useMemo, useCallback, or module scope) so the memoized child doesn't re-render every parent render
- [`react-doctor/no-large-animated-blur`](/prompts/rules/react-doctor/no-large-animated-blur.md) — Keep blur radius under 10px, or apply blur to a smaller element. Large blurs multiply GPU memory usage with layer size
- [`react-doctor/no-layout-property-animation`](/prompts/rules/react-doctor/no-layout-property-animation.md) — Use `transform: translateX()` or `scale()` instead — they run on the compositor and skip layout/paint
- [`react-doctor/no-layout-transition-inline`](/prompts/rules/react-doctor/no-layout-transition-inline.md) — Use `transform` and `opacity` for transitions — they run on the compositor thread. For height animations, use `grid-template-rows: 0fr → 1fr`
- [`react-doctor/no-long-transition-duration`](/prompts/rules/react-doctor/no-long-transition-duration.md) — Keep UI transitions under 1s — 100-150ms for instant feedback, 200-300ms for state changes, 300-500ms for layout changes. Use longer durations only for page-load hero animations
- [`react-doctor/no-permanent-will-change`](/prompts/rules/react-doctor/no-permanent-will-change.md) — Add will-change on animation start (`onMouseEnter`) and remove on end (`onAnimationEnd`). Permanent promotion wastes GPU memory and can degrade performance
- [`react-doctor/no-scale-from-zero`](/prompts/rules/react-doctor/no-scale-from-zero.md) — Use `initial={{ scale: 0.95, opacity: 0 }}` — elements should deflate like a balloon, not vanish into a point
- [`react-doctor/no-transition-all`](/prompts/rules/react-doctor/no-transition-all.md) — List specific properties: `transition: "opacity 200ms, transform 200ms"` — or in Tailwind use `transition-colors`, `transition-opacity`, or `transition-transform`
- [`react-doctor/no-unstable-nested-components`](/prompts/rules/react-doctor/no-unstable-nested-components.md) — Hoist nested components to module scope or memoize them — never define one inside another.
- [`react-doctor/no-usememo-simple-expression`](/prompts/rules/react-doctor/no-usememo-simple-expression.md) — Remove useMemo — property access, math, and ternaries are already cheap without memoization
- [`react-doctor/prefer-stable-empty-fallback`](/prompts/rules/react-doctor/prefer-stable-empty-fallback.md) — Hoist a module-level const EMPTY = [] (or {}) and use it as the || / ?? fallback so the memoised child sees a stable reference
- [`react-doctor/redux-useselector-inline-derivation`](/prompts/rules/react-doctor/redux-useselector-inline-derivation.md) — Select the raw slice in useSelector and derive with useMemo, or hoist into a memoised createSelector from reselect.
- [`react-doctor/redux-useselector-returns-new-collection`](/prompts/rules/react-doctor/redux-useselector-returns-new-collection.md) — useSelector that returns a fresh object/array literal re-renders on every action; return a primitive, split into multiple useSelector calls, or pass shallowEqual.
- [`react-doctor/rendering-animate-svg-wrapper`](/prompts/rules/react-doctor/rendering-animate-svg-wrapper.md) — Wrap the SVG: `<motion.div animate={...}><svg>...</svg></motion.div>`
- [`react-doctor/rendering-hoist-jsx`](/prompts/rules/react-doctor/rendering-hoist-jsx.md) — Move the static JSX to module scope: `const ICON = <svg>...</svg>` outside the component so it isn't recreated each render
- [`react-doctor/rendering-hydration-no-flicker`](/prompts/rules/react-doctor/rendering-hydration-no-flicker.md) — Use `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)` or add `suppressHydrationWarning` to the element
- [`react-doctor/rendering-script-defer-async`](/prompts/rules/react-doctor/rendering-script-defer-async.md) — Add `defer` for DOM-dependent scripts or `async` for independent ones (analytics). In Next.js, use `<Script strategy="afterInteractive" />` instead
- [`react-doctor/rendering-svg-precision`](/prompts/rules/react-doctor/rendering-svg-precision.md) — Truncate path/points/transform decimals to 1–2 digits — sub-pixel precision adds bytes with no visible difference
- [`react-doctor/rendering-usetransition-loading`](/prompts/rules/react-doctor/rendering-usetransition-loading.md) — Replace with `const [isPending, startTransition] = useTransition()` — avoids a re-render for the loading state
- [`react-doctor/rerender-defer-reads-hook`](/prompts/rules/react-doctor/rerender-defer-reads-hook.md) — Read the URL state inside the handler (e.g. `new URL(window.location.href).searchParams`) so the component doesn't subscribe and re-render on every URL change
- [`react-doctor/rerender-derived-state-from-hook`](/prompts/rules/react-doctor/rerender-derived-state-from-hook.md) — Use a threshold/media-query hook (e.g. `useMediaQuery("(max-width: 767px)")`) — the component re-renders only when the threshold flips, not every pixel
- [`react-doctor/rerender-functional-setstate`](/prompts/rules/react-doctor/rerender-functional-setstate.md) — Use the callback form: `setState(prev => prev + 1)` to always read the latest value
- [`react-doctor/rerender-lazy-ref-init`](/prompts/rules/react-doctor/rerender-lazy-ref-init.md) — Lazy-init the ref: `const ref = useRef(null); if (ref.current === null) ref.current = expensiveCall()`
- [`react-doctor/rerender-lazy-state-init`](/prompts/rules/react-doctor/rerender-lazy-state-init.md) — Wrap in an arrow function so it only runs once: `useState(() => expensiveComputation())`
- [`react-doctor/rerender-memo-before-early-return`](/prompts/rules/react-doctor/rerender-memo-before-early-return.md) — Extract the JSX into a memoized child component so the parent's early return short-circuits before the child renders
- [`react-doctor/rerender-memo-with-default-value`](/prompts/rules/react-doctor/rerender-memo-with-default-value.md) — Move to module scope: `const EMPTY_ITEMS: Item[] = []` then use as the default value
- [`react-doctor/rerender-state-only-in-handlers`](/prompts/rules/react-doctor/rerender-state-only-in-handlers.md) — Replace useState with useRef when the value is only mutated and never read in render — `ref.current = ...` updates without re-rendering the component
- [`react-doctor/rerender-transitions-scroll`](/prompts/rules/react-doctor/rerender-transitions-scroll.md) — Wrap the setState in startTransition (mark as non-urgent), use useDeferredValue, or stash in a ref + rAF throttle so scroll/pointer events don't trigger a re-render per fire
- [`react-doctor/tanstack-start-loader-parallel-fetch`](/prompts/rules/react-doctor/tanstack-start-loader-parallel-fetch.md) — Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` to avoid request waterfalls in route loaders

## Preact

- [`react-doctor/preact-no-children-length`](/prompts/rules/react-doctor/preact-no-children-length.md) — Wrap with toChildArray(children) from preact before reading .length or calling array methods on props.children.
- [`react-doctor/preact-no-react-hooks-import`](/prompts/rules/react-doctor/preact-no-react-hooks-import.md) — Import hooks from `preact/hooks` (or `preact/compat`), not `react`: import { useState } from "preact/hooks"
- [`react-doctor/preact-no-render-arguments`](/prompts/rules/react-doctor/preact-no-render-arguments.md) — Drop render's positional params and read this.props / this.state inside render() instead
- [`react-doctor/preact-prefer-ondblclick`](/prompts/rules/react-doctor/preact-prefer-ondblclick.md) — Rename onDoubleClick to onDblClick on host elements: <li onDblClick={openInline}> — Preact uses DOM event names
- [`react-doctor/preact-prefer-oninput`](/prompts/rules/react-doctor/preact-prefer-oninput.md) — Replace onChange with onInput on text-like inputs: onInput={(e) => setQuery(e.currentTarget.value)}

## React Compiler

- [`react-hooks-js/component-hook-factories`](/prompts/rules/react-hooks-js/component-hook-factories.md) — Deprecated: this rule has been removed in 7.1.0.
- [`react-hooks-js/error-boundaries`](/prompts/rules/react-hooks-js/error-boundaries.md) — Validates usage of error boundaries instead of try/catch for errors in child components
- [`react-hooks-js/globals`](/prompts/rules/react-hooks-js/globals.md) — Validates against assignment/mutation of globals during render, part of ensuring that [side effects must render outside of render](https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
- [`react-hooks-js/hooks`](/prompts/rules/react-hooks-js/hooks.md) — Validates the rules of hooks
- [`react-hooks-js/immutability`](/prompts/rules/react-hooks-js/immutability.md) — Validates against mutating props, state, and other values that [are immutable](https://react.dev/reference/rules/components-and-hooks-must-be-pure#props-and-state-are-immutable)
- [`react-hooks-js/incompatible-library`](/prompts/rules/react-hooks-js/incompatible-library.md) — Validates against usage of libraries which are incompatible with memoization (manual or automatic)
- [`react-hooks-js/preserve-manual-memoization`](/prompts/rules/react-hooks-js/preserve-manual-memoization.md) — Validates that existing manual memoized is preserved by the compiler. React Compiler will only compile components and hooks if its inference [matches or exceeds the existing manual memoization](https://react.dev/learn/react-compiler/introduction#what-should-i-do-about-usememo-usecallback-and-reactmemo)
- [`react-hooks-js/purity`](/prompts/rules/react-hooks-js/purity.md) — Validates that [components/hooks are pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure) by checking that they do not call known-impure functions
- [`react-hooks-js/refs`](/prompts/rules/react-hooks-js/refs.md) — Validates correct usage of refs, not reading/writing during render. See the "pitfalls" section in [`useRef()` usage](https://react.dev/reference/react/useRef#usage)
- [`react-hooks-js/set-state-in-effect`](/prompts/rules/react-hooks-js/set-state-in-effect.md) — Validates against calling setState synchronously in an effect. This can indicate non-local derived data, a derived event pattern, or improper external data synchronization.
- [`react-hooks-js/set-state-in-render`](/prompts/rules/react-hooks-js/set-state-in-render.md) — Validates against setting state during render, which can trigger additional renders and potential infinite render loops
- [`react-hooks-js/static-components`](/prompts/rules/react-hooks-js/static-components.md) — Validates that components are static, not recreated every render. Components that are recreated dynamically can reset state and trigger excessive re-rendering
- [`react-hooks-js/todo`](/prompts/rules/react-hooks-js/todo.md) — Unimplemented features
- [`react-hooks-js/unsupported-syntax`](/prompts/rules/react-hooks-js/unsupported-syntax.md) — Validates against syntax that we do not plan to support in React Compiler
- [`react-hooks-js/use-memo`](/prompts/rules/react-hooks-js/use-memo.md) — Validates usage of the useMemo() hook against common mistakes. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.
- [`react-hooks-js/void-use-memo`](/prompts/rules/react-hooks-js/void-use-memo.md) — Validates that useMemos always return a value and that the result of the useMemo is used by the component/hook. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.

## React Native

- [`react-doctor/expo-no-non-inlined-env`](/prompts/rules/react-doctor/expo-no-non-inlined-env.md) — Use static dotted access: process.env.EXPO_PUBLIC_NAME (computed/destructured reads aren't inlined and are undefined at runtime)
- [`react-doctor/rn-animate-layout-property`](/prompts/rules/react-doctor/rn-animate-layout-property.md) — Animate `transform: [{ translateX/Y }, { scale }]` and `opacity` instead of layout props — layout runs on the JS thread; transform/opacity run on the GPU compositor
- [`react-doctor/rn-animation-reaction-as-derived`](/prompts/rules/react-doctor/rn-animation-reaction-as-derived.md) — Replace useAnimatedReaction with `useDerivedValue(() => ..., [deps])` — shorter, native dependency tracking, no side-effect implication
- [`react-doctor/rn-bottom-sheet-prefer-native`](/prompts/rules/react-doctor/rn-bottom-sheet-prefer-native.md) — Use `<Modal presentationStyle="formSheet">` (RN v7+) for native gesture handling and snap points
- [`react-doctor/rn-detox-missing-await`](/prompts/rules/react-doctor/rn-detox-missing-await.md) — Prepend await to the Detox action, waitFor chain, or expect(element(...)) assertion: await element(by.id('submit')).tap()
- [`react-doctor/rn-list-callback-per-row`](/prompts/rules/react-doctor/rn-list-callback-per-row.md) — Hoist the handler with useCallback at list scope and pass the row id as a primitive prop, so the row's memo() shallow-compare actually hits
- [`react-doctor/rn-list-data-mapped`](/prompts/rules/react-doctor/rn-list-data-mapped.md) — Wrap the projection in `useMemo(() => items.map(...), [items])` so the list's `data` prop has a stable reference across parent renders
- [`react-doctor/rn-list-missing-estimated-item-size`](/prompts/rules/react-doctor/rn-list-missing-estimated-item-size.md) — Add estimatedItemSize so FlashList/LegendList sizes its initial container pool correctly: <FlashList data={items} estimatedItemSize={64} />
- [`react-doctor/rn-list-recyclable-without-types`](/prompts/rules/react-doctor/rn-list-recyclable-without-types.md) — Add `getItemType={item => item.kind}` so FlashList keeps separate recycle pools per item type — heterogeneous rows shouldn't share recycled cells
- [`react-doctor/rn-no-deep-imports`](/prompts/rules/react-doctor/rn-no-deep-imports.md) — Import from the "react-native" package root, not the deprecated "react-native/Libraries/..." subpath: import { Alert } from "react-native"
- [`react-doctor/rn-no-deprecated-modules`](/prompts/rules/react-doctor/rn-no-deprecated-modules.md) — Import from the community package instead — deprecated modules were removed from the react-native core
- [`react-doctor/rn-no-dimensions-get`](/prompts/rules/react-doctor/rn-no-dimensions-get.md) — Use `const { width, height } = useWindowDimensions()` — it updates reactively on rotation and resize
- [`react-doctor/rn-no-falsy-and-render`](/prompts/rules/react-doctor/rn-no-falsy-and-render.md) — Guard numeric-looking conditions: {count > 0 && <X/>}, {Boolean(count) && <X/>}, or {count ? <X/> : null}
- [`react-doctor/rn-no-image-children`](/prompts/rules/react-doctor/rn-no-image-children.md) — Replace <Image> with <ImageBackground> to render content over the image: <ImageBackground source={src}>...children...</ImageBackground>
- [`react-doctor/rn-no-inline-flatlist-renderitem`](/prompts/rules/react-doctor/rn-no-inline-flatlist-renderitem.md) — Extract renderItem to a named function or wrap in useCallback to avoid re-creating on every render
- [`react-doctor/rn-no-inline-object-in-list-item`](/prompts/rules/react-doctor/rn-no-inline-object-in-list-item.md) — Hoist style/object props outside renderItem (StyleSheet.create, useMemo at list scope, or pass primitives) so memo() row components stop bailing
- [`react-doctor/rn-no-legacy-expo-packages`](/prompts/rules/react-doctor/rn-no-legacy-expo-packages.md) — Migrate to the recommended replacement package — legacy Expo packages are no longer maintained
- [`react-doctor/rn-no-legacy-shadow-styles`](/prompts/rules/react-doctor/rn-no-legacy-shadow-styles.md) — Use `boxShadow` for cross-platform shadows on the new architecture instead of platform-specific shadow properties
- [`react-doctor/rn-no-non-native-navigator`](/prompts/rules/react-doctor/rn-no-non-native-navigator.md) — Use `@react-navigation/native-stack` (or `native-tabs` in v7+) for platform-native transitions and gestures
- [`react-doctor/rn-no-panresponder`](/prompts/rules/react-doctor/rn-no-panresponder.md) — Replace PanResponder with react-native-gesture-handler's Gesture.Pan(), which runs gesture math on the native UI thread instead of the JS thread.
- [`react-doctor/rn-no-raw-text`](/prompts/rules/react-doctor/rn-no-raw-text.md) — Wrap text in a `<Text>` component: `<Text>{value}</Text>` — raw strings outside `<Text>` crash on React Native
- [`react-doctor/rn-no-renderitem-key`](/prompts/rules/react-doctor/rn-no-renderitem-key.md) — Remove the no-op `key` from the JSX row that renderItem returns and set `keyExtractor` (or `item.key`) on the list instead.
- [`react-doctor/rn-no-scroll-state`](/prompts/rules/react-doctor/rn-no-scroll-state.md) — Track scroll position with a Reanimated shared value (`useAnimatedScrollHandler`) or a ref — `setState` on every scroll event causes re-render storms
- [`react-doctor/rn-no-scrollview-mapped-list`](/prompts/rules/react-doctor/rn-no-scrollview-mapped-list.md) — Use FlashList, LegendList, or FlatList — `<ScrollView>{items.map(...)}</ScrollView>` mounts every row in memory
- [`react-doctor/rn-no-set-native-props`](/prompts/rules/react-doctor/rn-no-set-native-props.md) — Drive the prop via state, an Animated.Value (useNativeDriver: true), or a Reanimated shared value instead of imperative setNativeProps
- [`react-doctor/rn-no-single-element-style-array`](/prompts/rules/react-doctor/rn-no-single-element-style-array.md) — Use `style={value}` instead of `style={[value]}` — single-element arrays add unnecessary allocation
- [`react-doctor/rn-prefer-content-inset-adjustment`](/prompts/rules/react-doctor/rn-prefer-content-inset-adjustment.md) — Drop the SafeAreaView wrapper and set `contentInsetAdjustmentBehavior="automatic"` on the ScrollView for native safe-area handling
- [`react-doctor/rn-prefer-expo-image`](/prompts/rules/react-doctor/rn-prefer-expo-image.md) — Use `<Image>` from `expo-image` instead of `react-native` — same prop API, plus disk + memory caching, placeholders, and crossfades
- [`react-doctor/rn-prefer-pressable`](/prompts/rules/react-doctor/rn-prefer-pressable.md) — Use `<Pressable>` from react-native (or react-native-gesture-handler) instead of legacy Touchable* components
- [`react-doctor/rn-prefer-pressable-over-gesture-detector`](/prompts/rules/react-doctor/rn-prefer-pressable-over-gesture-detector.md) — Replace tap-only `<GestureDetector>` with `<Pressable>`: <Pressable onPress={onPress}> or createCSSAnimatedComponent(Pressable) for animated press feedback
- [`react-doctor/rn-prefer-reanimated`](/prompts/rules/react-doctor/rn-prefer-reanimated.md) — Use `import Animated from 'react-native-reanimated'` — animations run on the UI thread instead of the JS thread
- [`react-doctor/rn-pressable-shared-value-mutation`](/prompts/rules/react-doctor/rn-pressable-shared-value-mutation.md) — Wrap in <GestureDetector gesture={Gesture.Tap()...}> so the press animation runs on the UI thread instead of bouncing across the JS bridge
- [`react-doctor/rn-scrollview-dynamic-padding`](/prompts/rules/react-doctor/rn-scrollview-dynamic-padding.md) — Use `contentInset={{ bottom: dynamicValue }}` — the OS applies it as an offset without reflowing the scroll content
- [`react-doctor/rn-scrollview-flex-in-content-container`](/prompts/rules/react-doctor/rn-scrollview-flex-in-content-container.md) — Replace `flex: <positive number>` on contentContainerStyle with `flexGrow: 1`
- [`react-doctor/rn-style-prefer-boxshadow`](/prompts/rules/react-doctor/rn-style-prefer-boxshadow.md) — Use the cross-platform CSS `boxShadow` string (RN v7+): `boxShadow: "0 2px 8px rgba(0,0,0,0.1)"` instead of platform-specific shadow*/elevation keys

## Security

- [`react-doctor/active-static-asset`](/prompts/rules/react-doctor/active-static-asset.md) — A browser-reachable SVG that contains a `<script>` tag or `on*` event handler runs that code in your origin when someone opens it, which can lead to cross-site scripting.
- [`react-doctor/agent-tool-capability-risk`](/prompts/rules/react-doctor/agent-tool-capability-risk.md) — An AI agent tool that can reach shell, filesystem, or network primitives lets prompt-injected input trigger those actions, because the model treats tool arguments as trusted.
- [`react-doctor/artifact-baas-authority-surface`](/prompts/rules/react-doctor/artifact-baas-authority-surface.md) — Shipping Firebase/Supabase client config with your collection and authorization-field names in a browser bundle hands attackers a map of your data model, which is dangerous when server-side rules do not enforce access.
- [`react-doctor/artifact-env-leak`](/prompts/rules/react-doctor/artifact-env-leak.md) — A real secret shipped in a browser bundle under a public env prefix (`NEXT_PUBLIC_`, `VITE_`, `REACT_APP_`, `EXPO_PUBLIC_`) is world-readable and must be treated as compromised.
- [`react-doctor/artifact-secret-leak`](/prompts/rules/react-doctor/artifact-secret-leak.md) — A live credential (API key, token, or connection string) sits in a browser bundle or static asset, so anyone can read it, and it must be treated as compromised.
- [`react-doctor/build-pipeline-secret-boundary`](/prompts/rules/react-doctor/build-pipeline-secret-boundary.md) — Installing dependencies while CI secrets are in the environment lets a malicious package's lifecycle script read those secrets, which risks supply-chain compromise.
- [`react-doctor/clickjacking-redirect-risk`](/prompts/rules/react-doctor/clickjacking-redirect-risk.md) — A redirect target taken from caller input, or a privileged page that allows untrusted framing, lets attackers send users to malicious sites or trick them through clickjacking.
- [`react-doctor/command-execution-input-risk`](/prompts/rules/react-doctor/command-execution-input-risk.md) — Passing caller-controlled input into a shell command lets an attacker run arbitrary commands on your server (remote code execution).
- [`react-doctor/cors-cookie-trust-risk`](/prompts/rules/react-doctor/cors-cookie-trust-risk.md) — Combining credentialed CORS with a wildcard or less-trusted origin, or scoping auth cookies to a parent domain, lets other sites or subdomains ride a user's session.
- [`react-doctor/dangerous-html-sink`](/prompts/rules/react-doctor/dangerous-html-sink.md) — Passing user- or request-derived data into an HTML sink like `dangerouslySetInnerHTML` or `innerHTML` without sanitizing it allows cross-site scripting.
- [`react-doctor/firebase-client-owned-authz-field`](/prompts/rules/react-doctor/firebase-client-owned-authz-field.md) — When the client writes ownership or role fields (`ownerId`, `orgId`, `role`, `isAdmin`) to Firebase/Supabase, an attacker can forge them and grant themselves access.
- [`react-doctor/firebase-permissive-rules`](/prompts/rules/react-doctor/firebase-permissive-rules.md) — A Firebase rule of `if true` or `if request.auth != null` leaves data open to everyone (or to every signed-in user), treating sign-in as authorization and exposing other users' data.
- [`react-doctor/firebase-query-filter-as-auth`](/prompts/rules/react-doctor/firebase-query-filter-as-auth.md) — Relying on a client-side Firestore `.where('userId', '==', …)` filter for access control is unsafe, because a client can drop the filter and read everyone's data.
- [`react-doctor/git-provider-url-injection-risk`](/prompts/rules/react-doctor/git-provider-url-injection-risk.md) — Interpolating request input into a Git provider URL without encoding lets an attacker inject extra path segments or parameters and redirect the request.
- [`react-doctor/iframe-missing-sandbox`](/prompts/rules/react-doctor/iframe-missing-sandbox.md) — Add sandbox="" (or a curated, minimal set of allow- tokens) to your iframe to restrict embedded content.
- [`react-doctor/import-metadata-execution-risk`](/prompts/rules/react-doctor/import-metadata-execution-risk.md) — Evaluating imported metadata or file contents (EXIF, manifests, presets, uploads, archives) as code lets an attacker achieve remote code execution.
- [`react-doctor/insecure-crypto-risk`](/prompts/rules/react-doctor/insecure-crypto-risk.md) — Weak primitives (MD5, SHA-1, DES, RC4), non-timing-safe comparisons, or `Math.random()` for security values make signatures, tokens, and passwords easier to forge or guess.
- [`react-doctor/jsx-no-target-blank`](/prompts/rules/react-doctor/jsx-no-target-blank.md) — Add rel="noreferrer" (or "noopener") whenever using target="_blank".
- [`react-doctor/key-lifecycle-risk`](/prompts/rules/react-doctor/key-lifecycle-risk.md) — A private key or release credential committed inline to the repo is exposed in git history and must be rotated and revoked.
- [`react-doctor/local-rpc-native-bridge-risk`](/prompts/rules/react-doctor/local-rpc-native-bridge-risk.md) — A localhost or native bridge that accepts loose origins and exposes install/update or shell commands lets a malicious web page drive native actions on the user's machine.
- [`react-doctor/mcp-tool-capability-risk`](/prompts/rules/react-doctor/mcp-tool-capability-risk.md) — An MCP tool runs with the connecting client's authority, so reaching shell, filesystem, or network primitives without validation lets injected input abuse them.
- [`react-doctor/mdx-ssr-execution-risk`](/prompts/rules/react-doctor/mdx-ssr-execution-risk.md) — Compiling untrusted MDX with the full pipeline runs attacker-supplied JSX and expressions on your server, which can lead to code execution.
- [`react-doctor/nextjs-no-side-effect-in-get-handler`](/prompts/rules/react-doctor/nextjs-no-side-effect-in-get-handler.md) — Move the side effect to a POST handler and use a <form> or fetch with method POST — GET requests can be triggered by prefetching and are vulnerable to CSRF
- [`react-doctor/no-eval`](/prompts/rules/react-doctor/no-eval.md) — Use `JSON.parse` for serialized data, `Function(...)` (still careful) for trusted templates, or refactor to avoid dynamic code execution
- [`react-doctor/no-secrets-in-client-code`](/prompts/rules/react-doctor/no-secrets-in-client-code.md) — Move secrets to server-only code. Public client environment variables are bundled into browser code and must not contain secrets
- [`react-doctor/nosql-injection-risk`](/prompts/rules/react-doctor/nosql-injection-risk.md) — Building a NoSQL query from raw client input lets an attacker inject operator-shaped keys or `$where` code and read or alter data they should not.
- [`react-doctor/package-metadata-secret`](/prompts/rules/react-doctor/package-metadata-secret.md) — A secret or public-prefixed secret name in `package.json` leaks easily, because package metadata is routinely published to registries, logs, and browser bundles.
- [`react-doctor/path-traversal-risk`](/prompts/rules/react-doctor/path-traversal-risk.md) — Building a filesystem path from request input lets an attacker use `..` or absolute paths to read or write files outside the intended directory.
- [`react-doctor/plugin-update-trust-risk`](/prompts/rules/react-doctor/plugin-update-trust-risk.md) — Downloading and running an update or plugin without verifying its integrity lets an attacker ship malicious code to your users.
- [`react-doctor/postmessage-origin-risk`](/prompts/rules/react-doctor/postmessage-origin-risk.md) — Reading `event.data` in a `message` handler without checking `event.origin` lets any other window send data your code trusts, which can lead to cross-site scripting or data theft.
- [`react-doctor/public-debug-artifact`](/prompts/rules/react-doctor/public-debug-artifact.md) — A browser-reachable debug, log, dump, or report file in your build output can expose source paths, internal routes, env data, or secrets.
- [`react-doctor/public-env-secret-name`](/prompts/rules/react-doctor/public-env-secret-name.md) — A public-prefixed env var whose name implies a secret (token, password, private key, service role) is inlined into the client bundle, so a real credential there is world-readable.
- [`react-doctor/raw-sql-injection-risk`](/prompts/rules/react-doctor/raw-sql-injection-risk.md) — Building a SQL query by string concatenation or an unsafe raw helper lets an attacker inject SQL and read or modify your database.
- [`react-doctor/repository-secret-file`](/prompts/rules/react-doctor/repository-secret-file.md) — A committed env file, credential, or token is exposed to anyone with repo access and must be rotated, even after you remove it.
- [`react-doctor/require-pnpm-hardening`](/prompts/rules/react-doctor/require-pnpm-hardening.md) — pnpm project is missing supply-chain hardening in pnpm-workspace.yaml — set `minimumReleaseAge`, keep `blockExoticSubdeps: true`, and set `trustPolicy: no-downgrade`
- [`react-doctor/supabase-client-owned-authz-field`](/prompts/rules/react-doctor/supabase-client-owned-authz-field.md) — When the client writes authorization columns (`ownerId`, `orgId`, `role`, `isAdmin`) to Supabase, an attacker can forge them and escalate their own access.
- [`react-doctor/supabase-rls-policy-risk`](/prompts/rules/react-doctor/supabase-rls-policy-risk.md) — A Supabase policy that disables row-level security, exposes the service role, or uses a `(true)` write predicate lets clients read or modify data that is not theirs.
- [`react-doctor/svg-filter-clickjacking-risk`](/prompts/rules/react-doctor/svg-filter-clickjacking-risk.md) — Applying CSS or SVG filters over a cross-origin iframe can be used for clickjacking or to read pixels from framed content the attacker should not see.
- [`react-doctor/tanstack-start-get-mutation`](/prompts/rules/react-doctor/tanstack-start-get-mutation.md) — Use `createServerFn({ method: 'POST' })` for data modifications — GET requests can be triggered by prefetching and are vulnerable to CSRF
- [`react-doctor/tanstack-start-no-secrets-in-loader`](/prompts/rules/react-doctor/tanstack-start-no-secrets-in-loader.md) — Loaders are isomorphic (run on both server and client). Wrap secret access in `createServerFn()` so it stays server-only
- [`react-doctor/tenant-static-proxy-risk`](/prompts/rules/react-doctor/tenant-static-proxy-risk.md) — Building an asset path from a client-supplied tenant, subdomain, or workspace value lets one tenant read another tenant's files.
- [`react-doctor/untrusted-redirect-following`](/prompts/rules/react-doctor/untrusted-redirect-following.md) — Following a redirect from a request-supplied URL without re-validating each hop lets an attacker bounce your server into internal addresses (server-side request forgery).
- [`react-doctor/url-prefilled-privileged-action`](/prompts/rules/react-doctor/url-prefilled-privileged-action.md) — Reading a privileged action from the URL (invite, role, permission, redirect, sharing) and acting on it lets an attacker craft a link that performs that action for a victim.
- [`react-doctor/webhook-signature-risk`](/prompts/rules/react-doctor/webhook-signature-risk.md) — An inbound webhook handler that acts on the request body without verifying the provider's signature will process forged requests from anyone.
- [`socket/low-supply-chain-score`](/prompts/rules/socket/low-supply-chain-score.md) — A direct dependency's worst Socket security axis (supply chain or vulnerability) scores below the configured minimum — bump it to a patched/healthier release, replace it, or vet it and raise `supplyChain.minScore`

## Server

- [`react-doctor/server-after-nonblocking`](/prompts/rules/react-doctor/server-after-nonblocking.md) — `import { after } from 'next/server'` then wrap: `after(() => analytics.track(...))` — response isn't blocked
- [`react-doctor/server-auth-actions`](/prompts/rules/react-doctor/server-auth-actions.md) — Add `const session = await auth()` at the top and throw/redirect if unauthorized before any data access
- [`react-doctor/server-cache-with-object-literal`](/prompts/rules/react-doctor/server-cache-with-object-literal.md) — Pass primitives to React.cache()-wrapped functions — argument identity (not deep equality) is the dedup key, so a fresh `{}` per render bypasses the cache
- [`react-doctor/server-dedup-props`](/prompts/rules/react-doctor/server-dedup-props.md) — Pass the source array once and derive the projection on the client — passing both doubles RSC serialization bytes
- [`react-doctor/server-fetch-without-revalidate`](/prompts/rules/react-doctor/server-fetch-without-revalidate.md) — Pass `{ next: { revalidate: <seconds> } }` (or `cache: "no-store"` / `next: { tags: [...] }`) so stale cached data doesn't silently persist
- [`react-doctor/server-hoist-static-io`](/prompts/rules/react-doctor/server-hoist-static-io.md) — Hoist the read to module scope: `const FONT_DATA = await fetch(new URL('./fonts/Inter.ttf', import.meta.url)).then(r => r.arrayBuffer())` runs once at module load
- [`react-doctor/server-no-mutable-module-state`](/prompts/rules/react-doctor/server-no-mutable-module-state.md) — Move per-request data into the action body, headers/cookies, or a request-scope (React.cache, AsyncLocalStorage). Module-scope `let`/`var` is shared across requests.
- [`react-doctor/server-sequential-independent-await`](/prompts/rules/react-doctor/server-sequential-independent-await.md) — Wrap independent awaits in `Promise.all([...])` so they race instead of waterfalling — second call doesn't depend on the first

## State & Effects

- [`effect/no-adjust-state-on-prop-change`](/prompts/rules/effect/no-adjust-state-on-prop-change.md) — Disallow adjusting state in an effect when a prop changes.
- [`effect/no-chain-state-updates`](/prompts/rules/effect/no-chain-state-updates.md) — Disallow chaining state changes in an effect.
- [`effect/no-derived-state`](/prompts/rules/effect/no-derived-state.md) — Disallow storing derived state in an effect.
- [`effect/no-event-handler`](/prompts/rules/effect/no-event-handler.md) — Disallow using state and an effect as an event handler.
- [`effect/no-initialize-state`](/prompts/rules/effect/no-initialize-state.md) — Disallow initializing state in an effect.
- [`effect/no-pass-data-to-parent`](/prompts/rules/effect/no-pass-data-to-parent.md) — Disallow passing data to parents in an effect.
- [`effect/no-pass-live-state-to-parent`](/prompts/rules/effect/no-pass-live-state-to-parent.md) — Disallow passing live state to parents in an effect.
- [`effect/no-reset-all-state-on-prop-change`](/prompts/rules/effect/no-reset-all-state-on-prop-change.md) — Disallow resetting all state in an effect when a prop changes.
- [`react-doctor/activity-wraps-effect-heavy-subtree`](/prompts/rules/react-doctor/activity-wraps-effect-heavy-subtree.md) — Audit the `<Activity>` subtree: every hide/show cycle tears down and recreates every `useEffect`/`useLayoutEffect` inside, so move subscriptions and effect-driven setState chains outside the boundary or pre-resolve the data above it.
- [`react-doctor/effect-needs-cleanup`](/prompts/rules/react-doctor/effect-needs-cleanup.md) — Return a cleanup function that releases the subscription / timer: `return () => target.removeEventListener(name, handler)` for listeners, `return () => clearInterval(id)` / `clearTimeout(id)` for timers, or `return unsubscribe` if the subscribe call already returned one
- [`react-doctor/hooks-no-nan-in-deps`](/prompts/rules/react-doctor/hooks-no-nan-in-deps.md) — Remove the literal NaN from the dependency array, or normalise it (Number.isNaN(x) ? 0 : x) before passing it in.
- [`react-doctor/jotai-derived-atom-returns-fresh-object`](/prompts/rules/react-doctor/jotai-derived-atom-returns-fresh-object.md) — Split the derivation into per-field primitive derived atoms, or wrap with selectAtom(source, fn, shallow) from jotai/utils when a wrapper object is required.
- [`react-doctor/jotai-select-atom-in-render-body`](/prompts/rules/react-doctor/jotai-select-atom-in-render-body.md) — Lift selectAtom to module scope, or wrap it: const a = useMemo(() => selectAtom(base, fn), [deps])
- [`react-doctor/jotai-tq-use-raw-query-atom`](/prompts/rules/react-doctor/jotai-tq-use-raw-query-atom.md) — Derive the field once, then subscribe to the derived atom: const dataAtom = atom((get) => get(queryAtom).data)
- [`react-doctor/no-cascading-set-state`](/prompts/rules/react-doctor/no-cascading-set-state.md) — Combine into useReducer: `const [state, dispatch] = useReducer(reducer, initialState)`
- [`react-doctor/no-derived-state-effect`](/prompts/rules/react-doctor/no-derived-state-effect.md) — For derived state, compute inline: `const x = fn(dep)`. For state resets on prop change, use a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect
- [`react-doctor/no-derived-use`](/prompts/rules/react-doctor/no-derived-use.md) — Don't pass a promise created during render into use(); create it in a Server Component (or a stable cache) so its reference stays stable across renders
- [`react-doctor/no-derived-useState`](/prompts/rules/react-doctor/no-derived-useState.md) — Remove useState and compute the value inline: `const value = transform(propName)`
- [`react-doctor/no-did-mount-set-state`](/prompts/rules/react-doctor/no-did-mount-set-state.md) — Derive state in getDerivedStateFromProps or initial state instead of calling this.setState in componentDidMount, which forces an extra render.
- [`react-doctor/no-did-update-set-state`](/prompts/rules/react-doctor/no-did-update-set-state.md) — Avoid calling this.setState in componentDidUpdate; derive the value with getDerivedStateFromProps to prevent re-render loops
- [`react-doctor/no-direct-state-mutation`](/prompts/rules/react-doctor/no-direct-state-mutation.md) — Replace the mutation with a setter call that produces a new reference: `setItems([...items, newItem])`, `setItems(items.filter(x => x !== target))`, `setItems(items.toSorted(...))`. React only re-renders on a new reference, so in-place updates are silently dropped
- [`react-doctor/no-effect-chain`](/prompts/rules/react-doctor/no-effect-chain.md) — Compute as much as possible during render (e.g. `const isGameOver = round > 5`) and write all related state inside the event handler that originally fires the chain. Each effect link adds an extra render and makes the code rigid as requirements evolve
- [`react-doctor/no-effect-event-handler`](/prompts/rules/react-doctor/no-effect-event-handler.md) — Move the conditional logic into onClick, onChange, or onSubmit handlers directly
- [`react-doctor/no-effect-event-in-deps`](/prompts/rules/react-doctor/no-effect-event-in-deps.md) — Call the useEffectEvent callback inside the effect body without listing it; its identity is intentionally unstable
- [`react-doctor/no-effect-with-fresh-deps`](/prompts/rules/react-doctor/no-effect-with-fresh-deps.md) — Move the constructed value into the hook body and depend on its primitive inputs, or memoize it with useMemo/useCallback so its reference is stable.
- [`react-doctor/no-event-trigger-state`](/prompts/rules/react-doctor/no-event-trigger-state.md) — Delete the trigger state (`useState(null)` plus the `useEffect` that watches it) and call the side-effect (`post(...)` / `navigate(...)` / `track(...)`) directly inside the event handler that previously called the setter. State should not exist purely to schedule effect runs
- [`react-doctor/no-fetch-in-effect`](/prompts/rules/react-doctor/no-fetch-in-effect.md) — Use `useQuery()` from @tanstack/react-query, `useSWR()`, or fetch in a Server Component instead
- [`react-doctor/no-mirror-prop-effect`](/prompts/rules/react-doctor/no-mirror-prop-effect.md) — Delete both the `useState` and the `useEffect` and read the prop directly during render. Mirroring a prop into local state forces a stale first render before the effect re-syncs
- [`react-doctor/no-mutable-in-deps`](/prompts/rules/react-doctor/no-mutable-in-deps.md) — Read mutable values (`location.pathname`, `ref.current`) inside the effect body instead of in the deps array, or subscribe with `useSyncExternalStore`. Mutations to these don't trigger re-renders, so listing them in deps doesn't make the effect react to changes
- [`react-doctor/no-mutating-reducer-state`](/prompts/rules/react-doctor/no-mutating-reducer-state.md) — Return a new reducer state object/array/collection instead of mutating the current state and returning the same top-level reference.
- [`react-doctor/no-prop-callback-in-effect`](/prompts/rules/react-doctor/no-prop-callback-in-effect.md) — Lift the shared state into a Provider so both sides read the same source — no useEffect-driven sync needed
- [`react-doctor/no-self-updating-effect`](/prompts/rules/react-doctor/no-self-updating-effect.md) — Break the self-updating-effect feedback loop: derive the value during render, move the write into an event handler, or guard the update so it provably converges.
- [`react-doctor/no-set-state-in-render`](/prompts/rules/react-doctor/no-set-state-in-render.md) — Move the setter call into a `useEffect`, an event handler, or replace the state with a value computed during render. Calling a setter at render time triggers another render, which calls the setter again — an infinite loop
- [`react-doctor/no-will-update-set-state`](/prompts/rules/react-doctor/no-will-update-set-state.md) — Don't call this.setState in componentWillUpdate — move the update to getDerivedStateFromProps or componentDidUpdate.
- [`react-doctor/prefer-use`](/prompts/rules/react-doctor/prefer-use.md) — Replace useContext(Context) with the React 19 use(Context) API, which reads the same value but may be called conditionally
- [`react-doctor/prefer-use-effect-event`](/prompts/rules/react-doctor/prefer-use-effect-event.md) — Wrap the callback with `useEffectEvent(callback)` (React 19+) and call the resulting binding from inside the sub-handler. The Effect Event captures the latest props/state without being a reactive dep, so the effect doesn't re-subscribe on every parent render. See https://react.dev/reference/react/useEffectEvent
- [`react-doctor/prefer-use-sync-external-store`](/prompts/rules/react-doctor/prefer-use-sync-external-store.md) — Replace the `useState(getSnapshot())` + `useEffect(() => store.subscribe(() => setSnapshot(getSnapshot())))` pair with `useSyncExternalStore(store.subscribe, getSnapshot)`. The hook handles tearing during concurrent renders and SSR snapshots; the manual subscribe pattern doesn't
- [`react-doctor/prefer-useReducer`](/prompts/rules/react-doctor/prefer-useReducer.md) — Group related state: `const [state, dispatch] = useReducer(reducer, { field1, field2, ... })`
- [`react-doctor/rerender-dependencies`](/prompts/rules/react-doctor/rerender-dependencies.md) — Extract to a useMemo, useRef, or module-level constant so the reference is stable

## TanStack Query

- [`react-doctor/query-destructure-result`](/prompts/rules/react-doctor/query-destructure-result.md) — Destructure the query result instead of binding the whole object: const { data, isLoading } = useQuery(...)
- [`react-doctor/query-mutation-missing-invalidation`](/prompts/rules/react-doctor/query-mutation-missing-invalidation.md) — Add `onSuccess: () => queryClient.invalidateQueries({ queryKey: ['...'] })` so cached data stays in sync after the mutation
- [`react-doctor/query-no-query-in-effect`](/prompts/rules/react-doctor/query-no-query-in-effect.md) — React Query manages refetching automatically via queryKey dependencies and the `enabled` option — manual refetch() in useEffect is usually unnecessary
- [`react-doctor/query-no-rest-destructuring`](/prompts/rules/react-doctor/query-no-rest-destructuring.md) — Destructure only the fields you need: `const { data, isLoading } = useQuery(...)` — rest destructuring subscribes to all fields and causes extra re-renders
- [`react-doctor/query-no-usequery-for-mutation`](/prompts/rules/react-doctor/query-no-usequery-for-mutation.md) — Use `useMutation()` for POST/PUT/DELETE — it provides onSuccess/onError callbacks, doesn't auto-refetch, and correctly models write operations
- [`react-doctor/query-no-void-query-fn`](/prompts/rules/react-doctor/query-no-void-query-fn.md) — queryFn must return a value for the cache. Use the `enabled` option to conditionally disable the query instead of returning undefined
- [`react-doctor/query-stable-query-client`](/prompts/rules/react-doctor/query-stable-query-client.md) — Move `new QueryClient()` to module scope or wrap in `useState(() => new QueryClient())` — recreating it on every render resets the entire cache

## TanStack Start

- [`react-doctor/tanstack-start-missing-head-content`](/prompts/rules/react-doctor/tanstack-start-missing-head-content.md) — Add `<HeadContent />` inside `<head>` in your __root route — without it, route `head()` meta tags are silently dropped
- [`react-doctor/tanstack-start-no-anchor-element`](/prompts/rules/react-doctor/tanstack-start-no-anchor-element.md) — `import { Link } from '@tanstack/react-router'` — enables type-safe routes, preloading via `preload="intent"`, and client-side navigation
- [`react-doctor/tanstack-start-no-direct-fetch-in-loader`](/prompts/rules/react-doctor/tanstack-start-no-direct-fetch-in-loader.md) — Use `createServerFn()` from @tanstack/react-start — provides type-safe RPC, input validation, and proper server/client code splitting
- [`react-doctor/tanstack-start-no-dynamic-server-fn-import`](/prompts/rules/react-doctor/tanstack-start-no-dynamic-server-fn-import.md) — Use `import { myFn } from '~/utils/my.functions'` — the bundler replaces server code with RPC stubs only for static imports
- [`react-doctor/tanstack-start-no-navigate-in-render`](/prompts/rules/react-doctor/tanstack-start-no-navigate-in-render.md) — Use `throw redirect({ to: '/path' })` in `beforeLoad` or `loader` instead — navigate() during render causes hydration issues
- [`react-doctor/tanstack-start-no-use-server-in-handler`](/prompts/rules/react-doctor/tanstack-start-no-use-server-in-handler.md) — TanStack Start handles server boundaries automatically via the Vite plugin — "use server" inside createServerFn causes compilation errors
- [`react-doctor/tanstack-start-no-useeffect-fetch`](/prompts/rules/react-doctor/tanstack-start-no-useeffect-fetch.md) — Fetch data in the route `loader` instead — the router coordinates loading before rendering to avoid waterfalls
- [`react-doctor/tanstack-start-redirect-in-try-catch`](/prompts/rules/react-doctor/tanstack-start-redirect-in-try-catch.md) — TanStack Router's `redirect()` and `notFound()` throw special errors caught by the router. Move them outside the try block or re-throw in the catch
- [`react-doctor/tanstack-start-route-property-order`](/prompts/rules/react-doctor/tanstack-start-route-property-order.md) — Follow the order: params/validateSearch → loaderDeps → context → beforeLoad → loader → head. See https://tanstack.com/router/latest/docs/eslint/create-route-property-order
- [`react-doctor/tanstack-start-server-fn-method-order`](/prompts/rules/react-doctor/tanstack-start-server-fn-method-order.md) — Chain methods in order: .middleware() → .inputValidator() → .client() → .server() → .handler() — types depend on this sequence
- [`react-doctor/tanstack-start-server-fn-validate-input`](/prompts/rules/react-doctor/tanstack-start-server-fn-validate-input.md) — Add `.inputValidator(schema)` before `.handler()` — data crosses a network boundary and must be validated at runtime
