Why LWC performance matters
Salesforce users tolerate 2-second page loads; at 3 seconds, adoption drops 40%. Lightning Web Components are fast by design (no framework abstraction, compiled templates), but poor patterns still cause slow pages: too many SOQL queries in wire adapters, synchronous Apex calls blocking the UI, unpaginated list views loading 2000 records, and reactive properties triggering re-renders on every keystroke. These 9 patterns keep your LWC pages sub-second.
Pattern 1: Use @wire with client-side caching
The @wire decorator caches responses automatically. If two components on the same page wire the same Apex method, only one SOQL fires. Always use @wire over imperative calls for data that doesn't change within a session. For data that does change, use refreshApex() to invalidate the cache explicitly — never bypass @wire with imperative calls 'just to be safe.'
Pattern 2: Lazy-load related data
Don't load all related records upfront. Use a 'Load more' button or infinite scroll that fetches the next 20 records via an imperative Apex call with an offset. The initial page load should show 20 records in <500ms; subsequent loads can take 1-2 seconds because the user expects them.
Pattern 3: Debounce search inputs
If a search input triggers a SOQL query, debounce it by 300-500ms. Without debouncing, typing 'Salesforce' fires 10 queries (S, Sa, Sal...). With debouncing, only one query fires after the user stops typing. Use a simple setTimeout/clearTimeout pattern or the debounce utility.
Pattern 4: Paginate server-side, not client-side
Never load 2000 records and paginate client-side — the initial load will take 10+ seconds and hit SOQL row limits. Instead, use SOQL OFFSET and LIMIT for server-side pagination. For large datasets (>1000 records), use SOQL query cursors (Database.getQueryLocator) which are more efficient than OFFSET.
Pattern 5: Minimize reactive properties
Every reactive property (@track or @api) triggers a re-render when it changes. If a form has 20 fields and each is reactive, typing in one field re-renders the entire form. Use a single reactive object and update it immutably, or use getters for derived values instead of storing them as separate reactive properties.
Pattern 6: Use LDS over Apex for record data
Lightning Data Service (LDS) via lightning-record-form, lightning-record-edit-form, or getRecord is cached, shared across components, and handles CRUD without Apex. It's 3-5x faster than a custom Apex call for the same record. Use Apex only for complex queries (joins, aggregates, external objects).
Pattern 7: Batch Apex for bulk operations
If a user action triggers >200 record updates, don't do it synchronously — use Batch Apex. The UI shows a toast ('Processing...') and the batch runs asynchronously. The user isn't blocked, and governor limits are respected (batches process 200 records per transaction).
Pattern 8: Avoid synchronous Apex in connectedCallback
connectedCallback runs before the component renders. If it calls Apex imperatively (without await), the component renders before data arrives — causing a flash of empty content. Use @wire for initial data, or use await in a connectedCallback async function with a loading spinner.
Pattern 9: Use CustomEvent for parent-child communication
Don't use a shared PubSub library for parent-child communication — use CustomEvent dispatch. It's synchronous, type-safe, and doesn't require a message channel. For sibling-to-sibling or cross-DOM communication, use Lightning Message Service (LMS) — but only when necessary.
"Salesforce users tolerate 2-second page loads; at 3 seconds, adoption drops 40%."
Key Takeaway
Use @wire with caching, lazy-load related data, debounce search inputs, paginate server-side, minimize reactive properties, prefer LDS over Apex, batch bulk operations, avoid synchronous Apex in connectedCallback, and use CustomEvent for parent-child comms.