Lightning Web Components are powerful, but it's easy to build components that frustrate users with slow rendering and laggy interactions. We've optimized dozens of LWCs, and here are the techniques that make the biggest difference.
Diagnosing Performance Issues
Before optimizing, identify what's actually slow:
Use Chrome DevTools Performance Tab
- Open DevTools (F12)
- Go to Performance tab
- Click Record, interact with your component, then Stop
- Look for long tasks (red triangles) and JavaScript execution time
Enable Lightning Debug Mode
In Setup → Debug Mode → Enable for your user. This gives you better stack traces and component timing.
Check the Lightning Inspector
The Salesforce Lightning Inspector Chrome extension shows component lifecycle events and performance metrics.
Problem 1: Too Many Wire Calls
Each @wire decorator makes an Apex call. Multiple wires mean multiple round trips:
// ❌ BAD: Three separate server calls
@wire(getAccount, { accountId: '$recordId' })
account;
@wire(getContacts, { accountId: '$recordId' })
contacts;
@wire(getOpportunities, { accountId: '$recordId' })
opportunities;
Combine them into one call:
// ✅ GOOD: One server call returns all data
@wire(getAccountWithRelated, { accountId: '$recordId' })
wiredData({ error, data }) {
if (data) {
this.account = data.account;
this.contacts = data.contacts;
this.opportunities = data.opportunities;
}
}
// Apex method
@AuraEnabled(cacheable=true)
public static AccountWrapper getAccountWithRelated(Id accountId) {
return new AccountWrapper(
[SELECT Id, Name, Industry FROM Account WHERE Id = :accountId],
[SELECT Id, Name, Email FROM Contact WHERE AccountId = :accountId LIMIT 100],
[SELECT Id, Name, Amount, StageName FROM Opportunity WHERE AccountId = :accountId LIMIT 50]
);
}
Problem 2: Rendering Large Lists
Rendering thousands of items kills performance. Use pagination or virtualization:
// ❌ BAD: Render all 5,000 records at once
<template for:each={allRecords} for:item="record">
<c-record-card key={record.Id} record={record}></c-record-card>
</template>
// ✅ GOOD: Paginate with "Load More"
<template for:each={visibleRecords} for:item="record">
<c-record-card key={record.Id} record={record}></c-record-card>
</template>
<template if:true={hasMore}>
<lightning-button label="Load More" onclick={handleLoadMore}></lightning-button>
</template>
// JavaScript
PAGE_SIZE = 50;
currentPage = 1;
get visibleRecords() {
return this.allRecords.slice(0, this.currentPage * this.PAGE_SIZE);
}
get hasMore() {
return this.visibleRecords.length < this.allRecords.length;
}
handleLoadMore() {
this.currentPage++;
}
Problem 3: Expensive Getters
Getters run on every render. Complex calculations in getters cause performance issues:
// ❌ BAD: Complex calculation runs on every render
get processedData() {
return this.rawData.map(item => {
return {
...item,
score: this.calculateComplexScore(item),
formatted: this.formatAllFields(item),
// More expensive operations...
};
});
}
// ✅ GOOD: Cache the result
_processedData;
get processedData() {
if (!this._processedData && this.rawData) {
this._processedData = this.rawData.map(item => {
return {
...item,
score: this.calculateComplexScore(item),
formatted: this.formatAllFields(item),
};
});
}
return this._processedData;
}
// Clear cache when raw data changes
@wire(getData)
wiredData({ data }) {
if (data) {
this.rawData = data;
this._processedData = null; // Invalidate cache
}
}
Problem 4: Unnecessary Re-renders
Changing any @track property triggers a re-render. Batch your updates:
// ❌ BAD: Three separate re-renders
this.isLoading = false;
this.data = result;
this.error = null;
// ✅ GOOD: Update object properties (single re-render)
this.state = {
...this.state,
isLoading: false,
data: result,
error: null
};
Or use a single state object from the start:
@track state = {
isLoading: false,
data: null,
error: null
};
// Update all at once
updateState(updates) {
this.state = { ...this.state, ...updates };
}
Problem 5: Missing Cacheable
If your Apex method returns the same data for the same inputs, make it cacheable:
// ❌ Missing caching - calls server every time
@AuraEnabled
public static List<Product__c> getProducts() {
return [SELECT Id, Name, Price__c FROM Product__c];
}
// ✅ With caching - uses client-side cache when possible
@AuraEnabled(cacheable=true)
public static List<Product__c> getProducts() {
return [SELECT Id, Name, Price__c FROM Product__c];
}
Important: Only use cacheable=true for methods that:
- Don't modify data
- Return consistent results for the same inputs
- Are okay with slightly stale data
Problem 6: Heavy DOM Operations
Manipulating the DOM directly (querying elements, changing styles) is expensive. Let the framework handle it:
// ❌ BAD: Direct DOM manipulation in a loop
this.template.querySelectorAll('.item').forEach(el => {
if (this.selectedIds.includes(el.dataset.id)) {
el.classList.add('selected');
} else {
el.classList.remove('selected');
}
});
// ✅ GOOD: Let the template handle it
<template for:each={items} for:item="item">
<div key={item.id} class={item.cssClass}>{item.name}</div>
</template>
// In JS, compute cssClass as part of data transformation
get items() {
return this.rawItems.map(item => ({
...item,
cssClass: this.selectedIds.includes(item.id) ? 'item selected' : 'item'
}));
}
Real Performance Wins
We recently optimized a client's account hierarchy viewer component:
Before optimization:
- Initial load: 4.2 seconds
- 5 separate wire calls
- Rendering 2,000 nodes at once
- Getters recalculating on every click
After optimization:
- Initial load: 0.8 seconds
- 1 combined wire call with caching
- Virtual scrolling showing only visible nodes
- Memoized calculations
80% faster load time, and users actually use the feature now.
Quick Wins Checklist
- ✅ Combine multiple wire calls into one
- ✅ Add
cacheable=trueto read-only Apex methods - ✅ Paginate lists with more than 100 items
- ✅ Cache expensive getter calculations
- ✅ Batch state updates to reduce re-renders
- ✅ Use
keyattribute infor:eachloops - ✅ Lazy load child components with
lwc:if
Need help optimizing your Lightning components? Our custom development services include performance optimization. Let's make your components fast.