Advertisement

LWC & Aura

LWC Performance Optimization — Patterns That Actually Matter (2026)

Published 11 June 2026 · 11 min read · Advanced

LWC performance optimization has a dirty secret: most slow Lightning pages are not slow because of the framework. They are slow because of chatty data patterns, unguarded render-cycle work, and lists rendered without restraint. This guide covers the patterns with real payoff — data layer first, render cycle second, lists third — and how to measure before touching any of it, current to 2026.

For the framework-level context of why LWC starts from a faster baseline than Aura, see LWC vs Aura in 2026 — this guide assumes you’re on LWC and want to use that headroom well.

Measure first, or you’re guessing

Before any optimization: open Chrome DevTools → Performance, record the actual slow interaction, and read the trace. Thirty seconds of profiling routinely invalidates a week of planned “optimizations.” The two patterns that dominate real traces:

  • Network waterfalls — sequential Apex calls where each waits for the previous, visible as a staircase in the Network panel
  • Long render tasks — heavy synchronous work in getters, renderedCallback or event handlers, visible as wide blocks on the main thread

Everything below maps to one of those two.

The data layer — where most time lives

cacheable=true is the single highest-leverage line

public with sharing class ProductController {
    @AuraEnabled(cacheable=true)
    public static List<Product2> getActiveProducts(String family) {
        return [SELECT Id, Name, Family FROM Product2
                WHERE Family = :family AND IsActive = true];
    }
}

Wired cacheable methods are served from the client-side cache on repeat invocations with the same parameters — no server round-trip at all. The constraint is the contract: cacheable methods are read-only; DML inside one throws. Split your controllers accordingly: cacheable reads, non-cacheable writes, and call refreshApex after a write to invalidate the stale read.

@wire by default, imperative by exception

@wire is reactive — change a reactive parameter and the data re-provisions automatically — and it participates in caching. Imperative calls run when you say so and skip the cache unless the method is cacheable. The working rule:

  • Reads that drive the UI@wire
  • User-initiated mutations and one-shot reads (button clicks, form submits) → imperative

Lightning Data Service before Apex for single records

getRecord/getFieldValue read from a page-wide shared cache: if the record detail page already loaded the Account, your component’s getRecord resolves without touching the server, and any update — yours or another component’s — propagates to every subscriber. For single-record access with known fields, custom Apex is usually the slower, more expensive reimplementation of what LDS already does.

Kill the waterfall

Three wires on independent data run in parallel for free. The waterfall appears when developers chain imperative calls — await one, then the next, then the next. If call B doesn’t need call A’s result, fire them together:

const [products, pricing] = await Promise.all([
    getActiveProducts({ family }),
    getPricing({ family })
]);

The render cycle — the silent multiplier

Getters run on every render

Every getter referenced in the template re-evaluates on every render cycle. This getter looks innocent and is a per-render sort of the entire array:

get sortedRows() {
    return [...this.rows].sort((a, b) => a.name.localeCompare(b.name));
}

Anything beyond trivial work should compute once, when the source changes — in the wire handler or setter — into a plain property the template reads.

renderedCallback fires after every render

The most common LWC infinite loop: renderedCallback mutates a reactive property → render → renderedCallback → render. One-time DOM work needs a guard:

renderedCallback() {
    if (this.hasInitialized) return;
    this.hasInitialized = true;
    // one-time DOM measurement / third-party init
}

Debounce user input

A search box wired to Apex on every keystroke sends a request per character. Standard fix, ~300ms trailing debounce:

handleSearch(event) {
    clearTimeout(this.debounceTimer);
    const term = event.target.value;
    this.debounceTimer = setTimeout(() => { this.searchTerm = term; }, 300);
}

With searchTerm as a reactive wire parameter, the wire then refires exactly once per pause in typing.

Lists — where pages go to die

  • Always key your iterations. for:each with a stable key lets the diff reuse DOM nodes; without meaningful keys, every re-render rebuilds every row.
  • Cap what you mount. A few hundred rows is the practical ceiling for naive rendering. Beyond it: pagination, or lightning-datatable with enable-infinite-loading to fetch and mount incrementally.
  • Mind per-row cost. A row containing five nested components costs five component instantiations × row count. Flatten heavy rows into markup; keep components for genuinely reusable interactive units.

The server side of large lists is its own discipline — query shape matters more than render shape at scale, covered in SOQL best practices for large data volumes.

Loading discipline

  • Lazy-load below-the-fold and conditional UI. A modal’s contents behind lwc:if aren’t instantiated until opened — free win, often forgotten.
  • Static resources load via loadScript/loadStyle in a guarded renderedCallback or on first use — not eagerly in connectedCallback for a library only one tab needs.
  • Images: native loading="lazy" on anything below the fold.

The priority order

When a Lightning page is slow, work this list top-down and stop when it’s fast enough:

  1. Profile the interaction (30 seconds, non-negotiable)
  2. Make reads cacheable and wired; verify repeat navigation hits cache
  3. Break call waterfalls with Promise.all
  4. Move heavy getter work to compute-on-change
  5. Guard renderedCallback; debounce inputs
  6. Key, paginate or virtualize every large list

In practice, items 2 and 3 fix the majority of real-world complaints — the framework was never the problem.

Test your knowledge — LWC & Aura

10 questions · Basic to Advanced

0 / 10 correct

Advertisement

Frequently asked questions

How do I make Apex calls faster in LWC?

Mark read-only Apex methods with @AuraEnabled(cacheable=true) and call them via @wire. Cacheable methods are served from the client-side cache on repeat calls, eliminating the server round-trip entirely — the fastest request is the one that never happens.

What is the difference between @wire and imperative Apex in LWC?

@wire is reactive and declarative: it re-invokes automatically when reactive parameters change and participates in client caching. Imperative calls run exactly when you invoke them and are not cached unless the method is cacheable and also wired elsewhere. Default to @wire for reads; go imperative for user-initiated writes and one-off reads.

Why is my LWC getter running so many times?

Getters referenced in the template are re-evaluated on every render cycle of the component. A getter doing heavy computation — sorting, filtering, formatting large arrays — repeats that work on each render. Compute once into a property when the source data changes instead.

Why should renderedCallback be guarded in LWC?

renderedCallback fires after every render of the component, not once. Unguarded logic inside it — especially anything that mutates reactive state — re-triggers rendering and can loop. Guard one-time work with a boolean flag.

How do I render large lists efficiently in LWC?

Always provide a stable key in for:each so the diffing engine reuses DOM nodes, paginate or virtualize anything beyond a few hundred rows, and let lightning-datatable's enable-infinite-loading handle incremental loading rather than rendering thousands of rows at once.

Does Lightning Data Service make getRecord faster than Apex?

Usually, for single-record access. LDS shares a client-side cache across all components on the page — if any component already loaded the record, yours reads it without a server trip, and updates propagate to every subscriber automatically.

Advertisement