Skip to content
Back to skills

684 Performance Guide E9100d1d

ASecurity

Comprehensive guide to optimizing Lightning Web Component performance, including dark mode implementation, lazy loading, and rendering optimization. ---

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
developmentjavascriptgojavabashtestingapiperformancedocumentation

Works with

  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 684-performance-guide_e9100d1d --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 684 Performance Guide E9100d1d?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 684 Performance Guide E9100d1d
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-684-performance-guide-e9100d1d/badge)](https://www.skillsdirectory.com/skills/tools-only-684-performance-guide-e9100d1d)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Performance Optimization Guide for LWC

Comprehensive guide to optimizing Lightning Web Component performance, including dark mode implementation, lazy loading, and rendering optimization.

---

## Table of Contents

1. [Dark Mode Implementation](#dark-mode-implementation)
2. [Rendering Performance](#rendering-performance)
3. [Lazy Loading](#lazy-loading)
4. [Data Management](#data-management)
5. [Event Optimization](#event-optimization)
6. [Memory Management](#memory-management)
7. [Bundle Size Optimization](#bundle-size-optimization)
8. [Performance Testing](#performance-testing)
9. [Common Anti-Patterns](#common-anti-patterns)

---

## Dark Mode Implementation

Dark mode is exclusive to SLDS 2 themes. Components must use global styling hooks to support light/dark theme switching.

### Complete SLDS 2 Color Token Reference

#### Surface Colors

| Token | Light Mode | Dark Mode | Purpose |
|-------|------------|-----------|---------|
| `--slds-g-color-surface-1` | `#FFFFFF` | `#0B0B0B` | Primary surface (body background) |
| `--slds-g-color-surface-2` | `#F3F3F3` | `#181818` | Secondary surface |
| `--slds-g-color-surface-3` | `#E5E5E5` | `#2B2B2B` | Tertiary surface |
| `--slds-g-color-surface-4` | `#C9C9C9` | `#3E3E3E` | Quaternary surface |

#### Container Colors

| Token | Light Mode | Dark Mode | Purpose |
|-------|------------|-----------|---------|
| `--slds-g-color-surface-container-1` | `#FAFAFA` | `#1A1A1A` | Card backgrounds, panels |
| `--slds-g-color-surface-container-2` | `#F7F7F7` | `#232323` | Nested containers |
| `--slds-g-color-surface-container-3` | `#F3F3F3` | `#2E2E2E` | Deep nesting |

#### Text Colors

| Token | Light Mode | Dark Mode | Purpose |
|-------|------------|-----------|---------|
| `--slds-g-color-on-surface` | `#181818` | `#FAFAFA` | Primary text |
| `--slds-g-color-on-surface-1` | `#444444` | `#C9C9C9` | Secondary text |
| `--slds-g-color-on-surface-2` | `#706E6B` | `#A0A0A0` | Muted/disabled text |
| `--slds-g-color-on-surface-inverse` | `#FFFFFF` | `#181818` | Inverse text (buttons, badges) |

#### Border Colors

| Token | Light Mode | Dark Mode | Purpose |
|-------|------------|-----------|---------|
| `--slds-g-color-border-1` | `#C9C9C9` | `#444444` | Primary borders |
| `--slds-g-color-border-2` | `#E5E5E5` | `#3E3E3E` | Secondary borders (dividers) |

#### Brand Colors

| Token | Light Mode | Dark Mode | Purpose |
|-------|------------|-----------|---------|
| `--slds-g-color-brand-1` | `#0176D3` | `#1B96FF` | Primary brand (buttons, links) |
| `--slds-g-color-brand-2` | `#014486` | `#0B5CAB` | Brand hover/active states |

#### Status Colors

| Token | Light Mode | Dark Mode | Purpose |
|-------|------------|-----------|---------|
| `--slds-g-color-success-1` | `#2E844A` | `#45C65A` | Success states |
| `--slds-g-color-error-1` | `#EA001E` | `#FE5C4C` | Error states |
| `--slds-g-color-warning-1` | `#FFB75D` | `#FFB75D` | Warning states |
| `--slds-g-color-info-1` | `#0176D3` | `#1B96FF` | Info states |

#### Spacing Tokens

| Token | Value (rem) | Value (px) |
|-------|-------------|------------|
| `--slds-g-spacing-0` | 0 | 0 |
| `--slds-g-spacing-1` | 0.125 | 2 |
| `--slds-g-spacing-2` | 0.25 | 4 |
| `--slds-g-spacing-3` | 0.5 | 8 |
| `--slds-g-spacing-4` | 0.75 | 12 |
| `--slds-g-spacing-5` | 1 | 16 |
| `--slds-g-spacing-6` | 1.5 | 24 |
| `--slds-g-spacing-7` | 2 | 32 |
| `--slds-g-spacing-8` | 3 | 48 |

### Migration Examples

#### Before: SLDS 1 (Hardcoded Colors)

```css
/* accountCard.css - SLDS 1 (Deprecated) */
.card {
    background-color: #ffffff;
    color: #333333;
    border: 1px solid #dddddd;
    border-radius: 4px;
    padding: 16px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.card-header {
    color: #000000;
    font-size: 16px;
    font-weight: 700;
    margin-bottom: 8px;
}

.card-text {
    color: #666666;
    font-size: 14px;
}

.card-link {
    color: #0176d3;
}

.card-link:hover {
    color: #014486;
    text-decoration: underline;
}
```

#### After: SLDS 2 (Dark Mode Ready)

```css
/* accountCard.css - SLDS 2 */
.card {
    background-color: var(--slds-g-color-surface-container-1, #ffffff);
    color: var(--slds-g-color-on-surface, #181818);
    border: 1px solid var(--slds-g-color-border-2, #e5e5e5);
    border-radius: var(--slds-g-radius-border-2, 0.25rem);
    padding: var(--slds-g-spacing-5, 1rem);
    box-shadow: 0 2px 4px var(--slds-g-color-border-1, rgba(0, 0, 0, 0.1));
}

.card-header {
    color: var(--slds-g-color-on-surface, #181818);
    font-size: var(--slds-g-font-size-5, 1rem);
    font-weight: var(--slds-g-font-weight-bold, 700);
    margin-bottom: var(--slds-g-spacing-3, 0.5rem);
}

.card-text {
    color: var(--slds-g-color-on-surface-1, #444444);
    font-size: var(--slds-g-font-size-3, 0.875rem);
}

.card-link {
    color: var(--slds-g-color-brand-1, #0176d3);
}

.card-link:hover {
    color: var(--slds-g-color-brand-2, #014486);
    text-decoration: underline;
}
```

### Component-Level Example

```javascript
// darkModeCard.js
import { LightningElement } from 'lwc';

export default class DarkModeCard extends LightningElement {
    // No JavaScript changes needed for dark mode!
    // All theming is handled via CSS variables
}
```

```html
<!-- darkModeCard.html -->
<template>
    <div class="card">
        <div class="card-header">
            <h2 class="card-title">Account Details</h2>
        </div>
        <div class="card-body">
            <p class="card-text">This card automatically adapts to light/dark mode</p>
            <a href="#" class="card-link">Learn more</a>
        </div>
    </div>
</template>
```

```css
/* darkModeCard.css */
.card {
    background-color: var(--slds-g-color-surface-container-1, #ffffff);
    border: 1px solid var(--slds-g-color-border-2, #e5e5e5);
    border-radius: var(--slds-g-radius-border-2, 0.25rem);
    padding: var(--slds-g-spacing-5, 1rem);
}

.card-header {
    border-bottom: 1px solid var(--slds-g-color-border-2, #e5e5e5);
    margin-bottom: var(--slds-g-spacing-4, 0.75rem);
    padding-bottom: var(--slds-g-spacing-3, 0.5rem);
}

.card-title {
    color: var(--slds-g-color-on-surface, #181818);
    font-size: var(--slds-g-font-size-5, 1rem);
    font-weight: var(--slds-g-font-weight-bold, 700);
    margin: 0;
}

.card-body {
    color: var(--slds-g-color-on-surface-1, #444444);
}

.card-text {
    margin-bottom: var(--slds-g-spacing-4, 0.75rem);
}

.card-link {
    color: var(--slds-g-color-brand-1, #0176d3);
    text-decoration: none;
}

.card-link:hover {
    color: var(--slds-g-color-brand-2, #014486);
    text-decoration: underline;
}
```

### Validation Script

```bash
# Check for hardcoded colors in CSS
grep -r "#[0-9A-Fa-f]\{3,6\}" force-app/main/default/lwc/ --include="*.css"

# Check for rgb/rgba values
grep -r "rgb\|rgba" force-app/main/default/lwc/ --include="*.css"

# Install SLDS Linter
npm install -g @salesforce-ux/slds-linter

# Run validation
slds-linter lint force-app/main/default/lwc/
```

---

## Rendering Performance

### Conditional Rendering

```javascript
// BAD: Re-renders entire list
<template for:each={allItems} for:item="item">
    <div if:true={item.visible} key={item.id}>
        {item.name}
    </div>
</template>

// GOOD: Filter before rendering
get visibleItems() {
    return this.allItems.filter(item => item.visible);
}

<template for:each={visibleItems} for:item="item">
    <div key={item.id}>{item.name}</div>
</template>
```

### Use `lwc:if` for Large Blocks

```html
<!-- lwc:if removes from DOM (better for large blocks) -->
<template lwc:if={showDashboard}>
    <c-dashboard data={dashboardData}></c-dashboard>
</template>

<!-- if:true hides with CSS (better for frequent toggling) -->
<div if:true={showMessage} class="message">
    {message}
</div>
```

### Key Directive for Lists

```html
<!-- CRITICAL: Use unique, stable keys -->
<template for:each={accounts} for:item="account">
    <div key={account.Id}>  <!-- Use record ID, not index -->
        {account.Name}
    </div>
</template>
```

### Getter Caching

```javascript
// BAD: Recalculates on every render
get formattedValue() {
    return this.expensiveCalculation(this.data);
}

// GOOD: Cache the result
@track _cachedValue;
_cacheKey;

get formattedValue() {
    const currentKey = JSON.stringify(this.data);
    if (this._cacheKey !== currentKey) {
        this._cacheKey = currentKey;
        this._cachedValue = this.expensiveCalculation(this.data);
    }
    return this._cachedValue;
}
```

### Avoid renderedCallback Loops

```javascript
// BAD: Infinite loop
renderedCallback() {
    this.count++; // Triggers re-render
}

// GOOD: Guard against loops
renderedCallback() {
    if (!this._rendered) {
        this._rendered = true;
        this.initializeChart();
    }
}
```

---

## Lazy Loading

### Dynamic Imports

```javascript
// accountManager.js
export default class AccountManager extends LightningElement {
    @track showCharts = false;
    chartModule;

    async handleShowCharts() {
        if (!this.chartModule) {
            // Lazy load chart component
            this.chartModule = await import('c/accountChart');
        }
        this.showCharts = true;
    }
}
```

### Intersection Observer for Lazy Loading

```javascript
// lazyImageLoader.js
export default class LazyImageLoader extends LightningElement {
    @api src;
    @api alt;

    isVisible = false;
    observer;

    renderedCallback() {
        if (!this.observer) {
            this.observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        this.isVisible = true;
                        this.observer.disconnect();
                    }
                });
            }, { rootMargin: '50px' });

            const img = this.template.querySelector('img');
            if (img) {
                this.observer.observe(img);
            }
        }
    }

    get imageSrc() {
        return this.isVisible ? this.src : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
    }

    disconnectedCallback() {
        if (this.observer) {
            this.observer.disconnect();
        }
    }
}
```

```html
<!-- lazyImageLoader.html -->
<template>
    <img src={imageSrc} alt={alt} loading="lazy">
</template>
```

### Virtual Scrolling

```javascript
// virtualList.js
export default class VirtualList extends LightningElement {
    @api items = [];
    @track visibleItems = [];

    itemHeight = 50;
    containerHeight = 500;
    scrollTop = 0;

    get visibleCount() {
        return Math.ceil(this.containerHeight / this.itemHeight);
    }

    get startIndex() {
        return Math.floor(this.scrollTop / this.itemHeight);
    }

    get endIndex() {
        return Math.min(
            this.startIndex + this.visibleCount + 1,
            this.items.length
        );
    }

    get paddingTop() {
        return this.startIndex * this.itemHeight;
    }

    get paddingBottom() {
        return (this.items.length - this.endIndex) * this.itemHeight;
    }

    connectedCallback() {
        this.updateVisibleItems();
    }

    handleScroll(event) {
        this.scrollTop = event.target.scrollTop;
        this.updateVisibleItems();
    }

    updateVisibleItems() {
        this.visibleItems = this.items.slice(
            this.startIndex,
            this.endIndex
        );
    }
}
```

```html
<!-- virtualList.html -->
<template>
    <div class="container"
         style={containerStyle}
         onscroll={handleScroll}>
        <div style={paddingTopStyle}></div>
        <template for:each={visibleItems} for:item="item">
            <div key={item.id} class="item">
                {item.name}
            </div>
        </template>
        <div style={paddingBottomStyle}></div>
    </div>
</template>
```

---

## Data Management

### Debouncing

```javascript
// searchComponent.js
export default class SearchComponent extends LightningElement {
    searchTerm = '';
    delayTimeout;

    handleSearchChange(event) {
        const searchTerm = event.target.value;

        // Clear previous timeout
        clearTimeout(this.delayTimeout);

        // Set new timeout (300ms debounce)
        this.delayTimeout = setTimeout(() => {
            this.performSearch(searchTerm);
        }, 300);
    }

    async performSearch(term) {
        try {
            const results = await searchAccounts({ searchTerm: term });
            this.results = results;
        } catch (error) {
            this.handleError(error);
        }
    }

    disconnectedCallback() {
        clearTimeout(this.delayTimeout);
    }
}
```

### Throttling

```javascript
// scrollTracker.js
export default class ScrollTracker extends LightningElement {
    lastScrollTime = 0;
    throttleDelay = 100;

    handleScroll(event) {
        const now = Date.now();

        if (now - this.lastScrollTime >= this.throttleDelay) {
            this.lastScrollTime = now;
            this.processScroll(event);
        }
    }

    processScroll(event) {
        // Handle scroll logic
        console.log('Scroll position:', event.target.scrollTop);
    }
}
```

### Caching Wire Results

```javascript
// accountList.js
export default class AccountList extends LightningElement {
    @api recordId;
    wiredAccountsResult;

    @wire(getAccounts, { accountId: '$recordId' })
    wiredAccounts(result) {
        this.wiredAccountsResult = result; // Cache for refreshApex
        if (result.data) {
            this.accounts = result.data;
        } else if (result.error) {
            this.error = result.error;
        }
    }

    async handleRefresh() {
        // Refresh cached wire result
        await refreshApex(this.wiredAccountsResult);
    }
}
```

---

## Event Optimization

### Event Delegation

```javascript
// BAD: Multiple event listeners
<template for:each={items} for:item="item">
    <button key={item.id} onclick={handleClick} data-id={item.id}>
        {item.name}
    </button>
</template>

// GOOD: Single delegated listener
<div onclick={handleContainerClick}>
    <template for:each={items} for:item="item">
        <button key={item.id} data-id={item.id}>
            {item.name}
        </button>
    </template>
</div>

handleContainerClick(event) {
    if (event.target.tagName === 'BUTTON') {
        const itemId = event.target.dataset.id;
        this.processClick(itemId);
    }
}
```

### Prevent Event Bubbling

```javascript
handleClick(event) {
    event.stopPropagation(); // Stop bubbling
    event.preventDefault();   // Prevent default action

    // Process event
}
```

---

## Memory Management

### Cleanup in disconnectedCallback

```javascript
export default class ResourceManager extends LightningElement {
    subscription;
    intervalId;
    observer;

    connectedCallback() {
        // Subscribe to events
        this.subscription = subscribe(
            this.messageContext,
            CHANNEL,
            this.handleMessage
        );

        // Set interval
        this.intervalId = setInterval(() => {
            this.updateData();
        }, 5000);

        // Create observer
        this.observer = new IntersectionObserver(
            this.handleIntersection
        );
    }

    disconnectedCallback() {
        // CRITICAL: Clean up all resources
        if (this.subscription) {
            unsubscribe(this.subscription);
            this.subscription = null;
        }

        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }

        if (this.observer) {
            this.observer.disconnect();
            this.observer = null;
        }
    }
}
```

### Remove Event Listeners

```javascript
export default class EventManager extends LightningElement {
    boundHandler;

    connectedCallback() {
        this.boundHandler = this.handleResize.bind(this);
        window.addEventListener('resize', this.boundHandler);
    }

    disconnectedCallback() {
        window.removeEventListener('resize', this.boundHandler);
    }

    handleResize() {
        // Handle resize
    }
}
```

---

## Bundle Size Optimization

### Code Splitting

```javascript
// Import only what you need
import { getRecord } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Account.Name';

// Don't import entire modules
// BAD: import * as uiRecordApi from 'lightning/uiRecordApi';
```

### Minimize Dependencies

```javascript
// BAD: Import heavy library for simple task
import moment from 'moment';

get formattedDate() {
    return moment(this.date).format('MM/DD/YYYY');
}

// GOOD: Use native APIs
get formattedDate() {
    return new Intl.DateTimeFormat('en-US').format(new Date(this.date));
}
```

---

## Performance Testing

### Chrome DevTools Performance Tab

```javascript
// Add performance marks
export default class PerformanceTracked extends LightningElement {
    connectedCallback() {
        performance.mark('component-start');
        this.initializeComponent();
        performance.mark('component-end');
        performance.measure(
            'component-initialization',
            'component-start',
            'component-end'
        );

        const measure = performance.getEntriesByName('component-initialization')[0];
        console.log('Initialization took:', measure.duration, 'ms');
    }
}
```

### Lighthouse Audit

```bash
lighthouse https://your-org.lightning.force.com --only-categories=performance
```

### Custom Performance Metrics

```javascript
export default class MetricsTracker extends LightningElement {
    connectedCallback() {
        // Track time to interactive
        const startTime = performance.now();

        this.loadData().then(() => {
            const endTime = performance.now();
            console.log('Time to interactive:', endTime - startTime, 'ms');
        });
    }
}
```

---

## Common Anti-Patterns

### 1. Excessive Wire Calls

```javascript
// BAD: Multiple wire calls for related data
@wire(getAccount, { accountId: '$recordId' }) account;
@wire(getContacts, { accountId: '$recordId' }) contacts;
@wire(getOpportunities, { accountId: '$recordId' }) opportunities;

// GOOD: Single wire call with joined data
@wire(getAccountWithRelated, { accountId: '$recordId' })
wiredData({ data, error }) {
    if (data) {
        this.account = data.account;
        this.contacts = data.contacts;
        this.opportunities = data.opportunities;
    }
}
```

### 2. Updating Tracked Properties in Getters

```javascript
// BAD: Side effects in getter
@track count = 0;

get message() {
    this.count++; // Causes infinite re-render!
    return `Count: ${this.count}`;
}

// GOOD: Pure getter
get message() {
    return `Count: ${this.count}`;
}
```

### 3. Not Using @track Wisely

```javascript
// BAD: Over-using @track
@track simpleValue = 'hello';
@track anotherValue = 42;

// GOOD: Only track complex objects
simpleValue = 'hello'; // Primitives don't need @track
@track complexObject = { nested: { value: 42 } };
```

### 4. Heavy Operations in renderedCallback

```javascript
// BAD: Heavy calculation every render
renderedCallback() {
    this.calculateComplexMetrics(); // Expensive!
}

// GOOD: Calculate only when data changes
@track _dataVersion = 0;
_renderedVersion = -1;

renderedCallback() {
    if (this._renderedVersion !== this._dataVersion) {
        this._renderedVersion = this._dataVersion;
        this.calculateComplexMetrics();
    }
}

handleDataChange() {
    this._dataVersion++;
}
```

---

## Performance Checklist

- [ ] Use SLDS 2 color tokens (dark mode ready)
- [ ] Lazy load components with dynamic imports
- [ ] Implement virtual scrolling for long lists (100+ items)
- [ ] Debounce search inputs (300ms)
- [ ] Throttle scroll/resize handlers (100ms)
- [ ] Cache expensive getter calculations
- [ ] Use `lwc:if` for large conditional blocks
- [ ] Provide stable keys in `for:each` loops
- [ ] Clean up resources in `disconnectedCallback()`
- [ ] Avoid heavy operations in `renderedCallback()`
- [ ] Use event delegation for list items
- [ ] Minimize wire service calls
- [ ] Remove unused imports
- [ ] Test with Chrome DevTools Performance tab
- [ ] Run Lighthouse performance audit

---

## Related Resources

- [component-patterns.md](component-patterns.md) - Implementation patterns
- [accessibility-guide.md](accessibility-guide.md) - A11y compliance
- [jest-testing.md](jest-testing.md) - Testing strategies
- [SLDS 2 Transition Guide](https://www.lightningdesignsystem.com/2e1ef8501/p/8184ad-transition-to-slds-2)
- [LWC Performance Best Practices](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.create_performance)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.