# Frontend Best Practices

> Apply pragmatic frontend engineering best practices while implementing, refactoring, or reviewing web UI code. Use for component architecture, module boundaries and barrel exports, higher-order components, hooks, state management, accessibility, performance, error handling, TypeScript, testing, and maintainability decisions in React, Next.js, or similar component-based applications.

- Skill: `hannanfikri/frontend-best-practices` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add hannanfikri/frontend-best-practices`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hannanfikri/frontend-best-practices/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: hannanfikri (https://skillmd.com/u/hannanfikri)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/hannanfikri/frontend-best-practices

---


# Frontend Best Practices

Improve frontend code without treating any pattern as universally correct. Preserve behavior, follow repository conventions, and optimize for correctness, clarity, accessibility, and maintainability before cleverness.

## Workflow

1. Read repository instructions, framework documentation, package versions, and nearby implementations before editing.
2. Identify the requested outcome, current behavior, public interfaces, and constraints.
3. Inspect the nearest established pattern and reuse existing components, utilities, tokens, and dependencies.
4. Choose the smallest design that solves the current problem and leaves a clear extension path.
5. Implement cohesive changes with explicit types at module boundaries.
6. Verify behavior, accessibility, type safety, linting, and relevant tests in proportion to risk. When a targeted unit is updated, update or add its unit tests in the same change unless the unit is intentionally untestable and explain why.
7. Report the outcome and distinguish new failures from pre-existing repository failures.

## Architecture

- Organize by feature or domain when code changes together; keep generic primitives in a shared layer.
- Keep data access, stateful orchestration, and presentation separable when doing so clarifies ownership or enables testing.
- Prefer composition and focused hooks over deeply configurable components.
- Keep state as local as possible. Lift it only when multiple consumers need a shared source of truth.
- Derive values during render when possible; do not synchronize derivable state with effects.
- Avoid speculative abstractions. Extract after a stable repeated concept appears, not merely repeated syntax.
- Preserve server/client boundaries. Do not promote a large subtree to client code for one interactive leaf.

## Components and Hooks

- Give each component one coherent responsibility, without enforcing arbitrary line limits.
- Prefer explicit props and children composition. Avoid boolean-prop combinations that create invalid states.
- Use discriminated unions when component modes require different props.
- Keep hook dependencies correct. Stabilize values only when identity has an observable cost or semantic requirement.
- Use effects only to synchronize with external systems; put user-triggered work in event handlers.
- Provide loading, empty, error, disabled, and success states where the workflow requires them.

## Component Modularity and Extraction

- Start with local components and extract when a section has a distinct responsibility, meaningful internal logic, independent state, repeated use, or a clear testing boundary.
- Keep tightly coupled markup together when extraction would only move JSX, create pass-through props, or force readers to jump between files without clarifying ownership.
- Move non-trivial component logic into custom hooks by default, especially form orchestration, state transitions, effects, subscriptions, async work, permission or visibility decisions, and repeated event-handler logic.
- Keep tiny pure render-time derivations inline or as local functions when a hook would only add indirection.
- Prefer small composed components over one highly configurable component with many boolean props and conditional branches.
- Place one-off feature components beside their parent. Promote them to the feature boundary only when multiple feature files consume them, and to shared UI only when they are domain-neutral and reused across features.
- Give extracted components explicit, minimal props based on the child responsibility. Pass domain objects when the child owns that domain concept; avoid forwarding a large parent state object for convenience.
- Keep server data loading and authorization in server components where possible, then pass serializable data to focused client components that own interaction.
- Avoid using file length or JSX line count as the extraction rule. Extract to improve ownership, reuse, testability, or readability—not to satisfy an arbitrary size limit.
- After extraction, check that dependencies flow in one direction, server/client boundaries remain intentional, names describe responsibilities, and the parent reads as a clear composition of the feature.

## Barrel Exports

Use barrel files deliberately:

- Use a feature-level `index.ts` to expose a small, intentional public API and hide internal files.
- Prefer named exports and type-only exports where appropriate.
- Avoid broad `export *` chains across many directories; they obscure ownership and invite naming collisions.
- Avoid barrels in dependency-cycle hotspots, initialization-sensitive modules, and server/client boundary code.
- Import from the feature barrel outside the feature; use direct relative imports inside the feature when that reduces cycles.
- Verify the bundler handles tree shaking before claiming barrels are performance-neutral.

Example public boundary:

```ts
export { AuditTable } from "./audit-table";
export type { AuditEvent, AuditCategory } from "./types";
```

## Higher-Order Components

Use an HOC when behavior must wrap many existing components or a framework API requires one. Prefer hooks or composition for new local behavior.

When implementing an HOC:

- Preserve the wrapped component's props with generics.
- Forward refs only when consumers need the underlying ref.
- Set a useful `displayName` for debugging.
- Do not mutate the wrapped component or silently overwrite unrelated props.
- Keep injected props explicit and prevent callers from supplying conflicting values.
- Hoist non-React statics only when consumers rely on them and use a proven utility.

```tsx
function withPermission<P extends object>(Component: React.ComponentType<P>) {
  function WithPermission(props: P) {
    return <Component {...props} />;
  }

  WithPermission.displayName = `withPermission(${Component.displayName ?? Component.name ?? "Component"})`;
  return WithPermission;
}
```

## TypeScript and Data

- Model domain states precisely; avoid `any`, unchecked assertions, and duplicated interfaces.
- Validate untrusted data at system boundaries. Static types do not validate runtime input.
- Keep transport models separate from view models when their shapes or lifecycles differ.
- Use stable identifiers for list keys; never use an array index when items can reorder.
- Treat URL state as the source of truth for shareable filters, pagination, tabs, and searches when appropriate.

## Testing

- When changing a targeted unit, inspect nearby existing tests before editing and update or add unit tests for changed behavior in the same change.
- Prefer focused tests for business logic, validation, permissions, data transformation, hooks, and API boundaries.
- Keep tests user-facing where possible; avoid asserting third-party internals, implementation details, or styling that is not business-critical.
- If no test is added for a changed unit, state the reason clearly in the handoff.

## Accessibility and UX

- Use semantic HTML before ARIA. Give every control an accessible name and visible focus state.
- Preserve keyboard operation, logical focus order, and focus restoration for dialogs and menus.
- Associate validation errors and instructions with their inputs.
- Do not rely on color alone to communicate status.
- Respect reduced motion and avoid unexpected layout shifts.
- Make responsive behavior work at narrow widths, zoomed layouts, and long localized text.

## Performance

- Measure or identify a credible bottleneck before optimizing.
- Prevent request waterfalls and duplicate fetching; choose caching based on freshness requirements.
- Keep rendering work proportional to visible UI. Paginate or virtualize genuinely large collections.
- Memoize expensive computations or identity-sensitive values, not every function and object.
- Lazy-load large optional experiences, while keeping critical interactions immediately available.
- Avoid importing large modules through convenience APIs when a direct import materially reduces the client bundle.

## Review Checklist

- Confirm behavior and edge cases match the request.
- Check component responsibilities, extraction boundaries, naming, duplication, dead code, and accidental API changes.
- Check semantic HTML, keyboard support, focus, labels, contrast, and status announcements.
- Check async cancellation, race conditions, stale closures, error recovery, and optimistic rollback.
- Check unnecessary client rendering, effects, rerenders, network calls, and bundle growth.
- Run focused tests first, then broader lint, type, and build checks when practical.
- Do not mix unrelated cleanup into a focused change.

## Decision Priority

Resolve conflicts in this order:

1. User requirements and repository instructions
2. Correctness and security
3. Accessibility
4. Maintainability and consistency
5. Performance supported by evidence
6. Personal style preference

