@wire in Lightning Web Components, Salesforce caches the results. If a component asks for the same data twice, it only costs one server trip. That gives you free speed, but it creates a risk of stale data. When you change data, you must manually call refreshApex to force a fresh copy. Lazy loading means you don't fetch or render heavy components until the user actually scrolls down to see them.
Screen A uses
@wire to fetch getOpenCases. A user opens Screen B, edits a case, and returns to Screen A. The list on Screen A is now stale, because it is being served from the local wire cache rather than the server. To fix this, store the entire wired result object (wiredCases) and call refreshApex(this.wiredCases) immediately after the edit in Screen B completes. The cache gives you speed; the refresh gives you the truth.
๐ฌ Real-Life Example: The List That Would Not Refresh
setInterval timer that called the Apex method every ten seconds. This burned massive server limits by polling constantly and still showed stale data for up to ten seconds.
refreshApex on that stored result. This forces one targeted refresh at the exact moment the data changes, allowing the cache to do its job the rest of the time without burning server limits.
๐ง The Core Concepts: Cacheable Data & LWC
Data flowing through an @wire decorator, whether from a UI API adapter (like getRecord) or an @AuraEnabled(cacheable=true) Apex method, goes straight through the Lightning Data Service (LDS) client-side cache.
- Shared Cache: Repeated identical requests are served locally. If twelve different components on a page wire the same record, Salesforce only makes one trip to the server.
- Cacheable Rules: Marking an Apex method as
cacheable=trueforbids any Data Manipulation Language (DML) like insert or update. It must be a pure read. - Forcing a Refresh: Use
refreshApex(wiredResult)to force a re-provision of a cached Apex method after you make imperative changes. UsenotifyRecordUpdateAvailable(recordIds)if you imperatively update a record that is being monitored by standard LDS adapters. - Lazy Loading: Refers to both data (pagination, load-more scrolling) and code (using dynamic imports to only load modules when a user interacts with them).
refreshApex is the bridge that links them together.
๐งญ 360 Card — Wire Caching & Lazy Loading
@wire requests are cached, multiple components asking for the same data only cost one server trip. You must refresh the cache manually when you change data.๐ Gain: Massive performance boosts for the end user and dramatic reductions in server load/API calls.
๐ ️ Reach for: Let the cache do the work first. Reach for
refreshApex only after YOU change the data. Reach for lazy loading only when a page has more than a handful of components or heavily nested data.๐ฐ Price: Staleness. The local cache cannot see writes made by other users, background integrations, or your own imperative Apex methods unless you tell it.
๐ Limits: A cacheable Apex method will throw an exception if you try to put DML inside it. Also,
refreshApex requires you to pass the entire wired result object, not just the .data property inside it.
๐ Core Q&A & Scenarios
refreshApex(wiredResult) for Apex, and notifyRecordUpdateAvailable(recordIds) for LDS standard records.
Detailed breakdown:
- The cache goes stale if you run imperative Apex DML, if another user edits the record, or if a backend integration writes to the record. The local cache is blind to all of these.
- To fix it: Pass the full provisioned object to
refreshApex(this.wiredValue). - If you imperatively touched standard records that the UI API serves, use
notifyRecordUpdateAvailable(recordIds)to tell LDS to fetch a fresh copy. - If you need to instantly know about another user's changes, you must use Change Data Capture (CDC) or
empApisubscriptions.
@AuraEnabled(cacheable=true) method throw an error when you add an insert statement? How do you fix the design?A: A cacheable method must be a pure read. Salesforce enforces this rule strictly: no DML is allowed. Think about it—if the method was cached, and the user clicked "Save" a second time, the cache would return a hit, skip the server trip, and the database insert would never happen.
The Fix (CQRS pattern): Split your commands from your queries. Write a non-cacheable imperative method to perform the DML mutation. Once that promise resolves, call refreshApex on your separate, cacheable @wire method to fetch the newly updated list.
A: Always measure with browser profiling tools first. Then, work in this exact order:
- 1. Dedupe: Ensure those 12 components aren't independently calling identical imperative Apex. If they all use
@wire(getRecord), they will share the LDS cache and reduce 12 round trips down to 1. - 2. Defer (Lazy Load): Push below-the-fold components into Lightning App Builder Accordions or Tabs. Inactive tabs do not mount their components until clicked.
- 3. Dynamic Imports: Defer loading massive third-party JavaScript libraries using dynamic imports until the exact moment they are needed.
- 4. Paginate: Implement "load-more" scrolling so you only fetch 50 rows of data at a time.
- 5. Check Loops: Verify you don't have infinite render loops happening inside
renderedCallbackmasquerading as a slow network load.
While both are caches, they live on entirely different floors of the architecture. The @wire / LDS cache lives in the user's browser, lasts only for their session, and is managed automatically by Salesforce. The Platform Cache lives on the Salesforce servers, is shared across all users, and is managed manually by your Apex code. They are not interchangeable concepts.