Development

Lightning Web Components best practices, from 100+ implementations

Umar Abdul Aziz 5 min read

Lightning Web Components adoption has been widespread for years now, but there’s a real gap between components that work in a demo and components that hold up in production under real data volume and real users. After building and reviewing LWC across more than 50 implementations, the same handful of patterns separate the two — and the same handful of mistakes show up again and again.

1. Minimize wire adapter calls; cache where possible

Why it matters: excess server round-trips are the single most common cause of a component that feels sluggish. Every additional @wire call is a network request the user waits on, whether they notice it consciously or not.

Pattern: use @wire deliberately — one call that returns everything a component needs beats three calls that each return a slice of it. Where the data doesn’t need to be live, cache results with Lightning Data Service rather than re-fetching on every render.

// Prefer one composed wire over three separate calls
@wire(getRecord, { recordId: '$recordId', fields: ACCOUNT_FIELDS })
account;

// Rather than three separate imperative Apex calls
// triggered on every re-render.

2. Keep components small and single-purpose

Why it matters: a component that does five things becomes untestable and unreusable. The bug fix for one responsibility risks breaking the other four, and nobody wants to touch it.

Pattern: compose small components rather than building one component that renders the header, fetches the data, handles the form, and manages the modal. Each piece should be independently testable.

3. Handle errors explicitly, not silently

Why it matters: a component that fails silently erodes user trust faster than one that shows a clear, honest error message. Silent failure means the user doesn’t know whether to retry, wait, or escalate.

Pattern: wrap wire and imperative calls in explicit error handling with a visible error state — not a blank panel, not a console-only log.

@wire(getRecords)
wiredRecords({ data, error }) {
  if (data) {
    this.records = data;
    this.error = undefined;
  } else if (error) {
    this.error = 'Could not load records. Try again.';
    this.records = undefined;
  }
}

4. Write Jest tests that verify behavior, not just render without crashing

Why it matters: coverage without meaningful assertions gives false confidence — a test suite that’s green but only checks "did it mount" won’t catch a broken interaction.

Pattern: test user interactions and edge cases — what happens on an empty result set, what happens when the user clicks twice quickly, what happens when the wire returns an error — not just the happy-path render.

Common mistakes

  • Overusing imperative Apex calls where a declarative @wire would cache and refresh more cleanly.
  • Not debouncing user input in search components, so every keystroke fires a server call.
  • Ignoring accessibility — missing labels on custom inputs, and focus management that leaves keyboard users stranded after a modal closes.

Performance tips

Lazy-load heavy components so they don’t cost anything until the user actually needs them. Avoid unnecessary re-renders by managing reactive properties carefully — a @tracked object that gets reassigned on every wire response will re-render more than it needs to. And use platform caching where the data allows it; not every field needs to be fetched fresh on every page load.

The pattern underneath all of this: build the component you’d want to inherit, not just the one that passes code review today.

Umar Abdul Aziz
Salesforce Architect · 11× certified

I design, build, and rescue enterprise Salesforce implementations — architecture, integrations, and custom development that hold up past the demo.

Work with me
Link copied