Lightning Components Performance Best Practices

Lightning Components run at the client-side, in a single page (where they are created and destroyed as needed), and alongside other components that work on the same data. In this blog post, we discuss how these characteristics impact performance, and review a list of best practices to optimize the performance of your Lightning Components.

If you are just starting with Lightning components, consider completing the following Trailhead modules before diving deeper:

The list of performance best practices discussed in this document includes:

Data retrieval

Optimize server round-trips:

  • Before making a call to the server, make sure there’s no other option to obtain the data.
  • When appropriate, consider passing data between components (using attributes, events, or methods) rather than retrieving the same data in different components. The Lightning Data Service (currently in Developer Preview and in Beta in Summer ’17) also allows you to efficiently share data between components.
  • When making a call to the server, limit the columns and rows of the result set:
    • Only SELECT the columns you need.
    • Set a LIMIT on the query and provide a paging mechanism if needed. Don’t return huge numbers of rows at once.
  • Lazy load occasionally accessed data. Don’t preload data that the user may never ask for (for example, data hidden behind a tab the user may not click, or in a combobox the user may not open).
  • Client-side filtering and sorting: Don’t make a call to the server to filter or sort data you already have at the client-side.
  • Consider combining several requests (actions) in a single composite request.
  • Cache data when possible (see Data caching).

Data caching

Application composition is a powerful way to build apps by assembling self-contained components. However, without proper planning, the autonomous nature of the components you assemble can have an adverse impact on performance. For example, if all the components you build make their own isolated calls to the server to retrieve the data they need, you’ll probably end up with lots of redundant server calls, which can dramatically impact performance.

Client-side data caching can solve that problem by sharing data among components. This can significantly reduce the number of server round-trips, and improve the performance of your Lightning components. The Lightning Component Framework has two built-in mechanisms for client-side caching (storable actions and Lightning Data Service). You can also implement a custom caching solution.

Storable actions

A storable action is a server action whose response is stored in the client cache so that subsequent requests for the same server method with the same set of arguments can be accessed from that cache. Server actions enable you to access data using a traditional rpc approach: You implement some logic in Apex that you expose as a remotely invocable method. Storable actions enable you to cache virtually anything (whatever the server method call returns): a record, a collection of records, a composite object, a custom data structure, data returned by a callout to a third-party service, and so on.

The general guideline is to cache (mark as storable) any action that is idempotent and non-mutating.

More information:

Lightning Data Service

Lightning Data Service provides a managed record approach: you’re not responsible for writing data access logic (no Apex). The framework is responsible for managing records: fetching them from the server when requested the first time, storing them in a highly efficient client cache, sharing them between all components that request them, and sending changes to the server. Lightning Data Service also handles progressive loading: it only loads the requested fields. If another component subsequently requires additional fields, these fields are loaded transparently and added to the record in the cache. Unlike storable actions that can cache any type of response returned by an Apex method, Lightning Data Service caches discrete Salesforce sObjects (record collections and other data types like metadata are on the roadmap). Lightning Data Service also improves user interface consistency: when a component updates a record, all the other components using that record are notified, and in most cases refreshed automatically.

More information:

Check out the following components in DreamHouse to see the Lightning Data Service in action: PropertyMap, PropertySummary, MortgageCalculator.

Custom cache

You can also implement your own custom cache approach. As always, make sure you don’t reinvent the wheel and only use a custom cache approach when there is no standard way to implement your caching requirements in the framework. For example, a custom cache can be a good solution for static data like a list of states in the United States, picklist values, and so on. You can use a storable action in this case too, but it still generates unneeded server calls (every time the data in the cache is older than the refreshAge, currently set to 30 seconds in the framework). If you know that the data won’t change, or changes infrequently, you can implement a custom cache solution that retrieves the data once after the user logs in and then never goes back to the server.

Check out the Mutual Fund Explorer app for an example of a custom cache. Open the DataCache static resource (File > Open > Static Resource > Data Cache in the developer console). And then check out the SectorSelector and AssetClassSelector components to see how it’s used to cache the list of sectors and asset classes.

More information:

Read the Modularizing Code in Lightning Components blog post for strategies to implement a custom cache.

Summary of client-side caching options:

Caching Requirements Recommended Solution
Single record Lightning Data Service
Collections of records, composite responses, custom data structures, third-party data Storable actions
Complete control over caching implementation Custom cache

Component instantiation

Showing every available piece of data and every available tool on the screen just in case the user may need it is generally not considered a good UI practice. It can also significantly impact the performance of your application. Today’s interactive design guidelines favor progressive disclosure.

“Progressive disclosure is an interaction design technique often used in human computer interaction to help maintain the focus of a user’s attention by reducing clutter, confusion, and cognitive workload. This improves usability by presenting only the minimum data required for the task at hand” (Wikipedia). Put another way, “progressive disclosure defers advanced or rarely used features to a secondary screen, making applications easier to learn and less error-prone” (Jakob Nielsen).

In Lightning Experience, it’s easy to implement progressive disclosure and defer the data or features that aren’t essential to the task at hand. Let’s take a closer look at two approaches to defer component creation.

Lazy instantiation in Lightning Experience

Lightning Components added to a page layout are instantiated when the page is loaded, contributing to the overall loading time of the page. For the right use cases, components can be hosted in specific areas in the Lightning Experience where they are lazily instantiated:

  • Quick or Global Actions
  • Utility Bar
  • App Builder tabs

Lazy instantiation in your own components

  • You can use <aura:if> to lazily instantiate parts of the UI (see conditional rendering section).
  • Some components (like <lightning:tabset> and <lightning:tab>) support lazy instantiation by default.
  • You can also instantiate components programmatically using $A.createComponent(); however, that approach is generally harder to code, maintain, and debug.

Conditional rendering

There are two popular techniques to conditionally render UI elements:

Toggling visibility using CSS

For example:

<div aura:id="error" class="slds-hide">{!v.errorMessage}</div>
cmp.find("error").toggleClass(!isError, "slds-hide");

or

<div class="{! v.isError ? null : 'slds-hide' ">{!v.errorMessage}</div>

The <div> component and all of its children are created and rendered up front, but they’re hidden. If there are event listeners attached to the <div> or any of its children, these event listeners would be “live.”

Conditionally creating elements using <aura:if>

For example:

<aura:if isTrue="{!v.isError}">
    <div>{!v.errorMessage}</code>
</aura:if>

In this case, the <div> component and all its children are only created if the value of the isTrue expression evaluates to true. All the components inside the <aura:if> tag are destroyed if the value of the isTrue expression changes and evaluates to false. They’re re-created the next time the value of the isTrue expression changes again and evaluates to true.

Guideline: use aura:if

The general guideline is to use the <aura:if> approach because it helps your components load faster initially by deferring the creation and rendering of the enclosed element tree until the condition is fulfilled. Components inside an <aura:if> with the value of its isTrue expression evaluating to false aren’t created and aren’t part of the DOM. Event listeners aren’t created either.

NOTE: The <aura:renderIf> approach is deprecated and shouldn’t be used.

More information:

In the DreamHouse sample application:

  • The PictureGalleryCard and Map components use <aura:if> to toggle between regular and full-screen mode.
  • The PropertySummary and SmartPriceCalculator components use <aura:if> to defer the instantiation of their body until a property is selected when used in a master/details interface.

Data binding

There are two ways to establish a connection between UI elements and attributes: bound and unbound expressions.

  • Bound expressions provide an implementation of bidirectional data binding. Bidirectional data binding provides a convenient and powerful abstraction. However it also comes at a cost: The framework has to set up two event listeners for each bound expression, which can have a negative impact on performance when there is a large number of bound expressions. It also creates an entanglement between the model and the view that can make your code harder to maintain and issues harder to debug in non-trivial situations.
  • Unbound expressions can have a positive impact on performance because they don’t maintain event listeners. However, they may not be the right solution for use cases where the UI has to be kept in sync with a changing model.

In the DreamHouse sample application, components created inside <aura:iteration> use unbound expressions to avoid the proliferation of event listeners. See PropertyListDaysOnMarketChart for an example. Also see how PropertyTile is bound to property objects inside PropertyTileList.

If you use data binding as a way to execute logic in a child component, consider using <aura:method> instead to provide your component with a public API. Check out the Mutual Fund Explorer app for an example of using <aura:method>. The FundInfoPopup component provides an API (showPopup and hidePopup) to show and hide the popup.

More information:
Read the Lightning Inter-Component Communication Patterns blog post for more details on inter-component communication patterns in general.

Lists (aura:iteration)

When creating custom list components (typically using <aura:iteration>), don’t support the creation of an infinite number of list items. Either provide a pagination mechanism, or virtualize the list (reuse and rehydrate a limited number of list item components).

Check out the Paginator component along with the PropertyController Apex class in DreamHouse for an example of a paginated list.

Events

Minimize the number of event handlers:

  • Use unbound expressions when possible.
  • Use <aura:if> when you need to conditionally render UI elements. Components inside an <aura:if> with its isTrue expression evaluating to false aren’t created, which means that event listeners attached to these components aren’t created either.
  • Limit the use of Application Events to coarse-grained application-level communication. For example: Communication between components added to pages in App Builder.
  • For finer-grained communication between components:
    • Use Component Events for child-to-parent communication:
      <Paginator onPageNext="{!c.pageNextHandler}" 
                 onPagePrevious="{!c.pagePreviousHandler}"/>
      
    • Use attributes or <aura:method> for parent-to-child communication
  • When working with lists, letting events bubble, and registering a single event listener on a parent element instead of a separate event listener on every list item can significantly reduce the number of event listeners in your application, which can have a positive impact on performance. In the Mutual Fund Explorer application check out the FundTileList component, and see how a single onmousemove event listener is registered on the list element (<ul>) instead of a separate listener on every list item (<li>) (inside the FundTile component).

More information:

  • In DreamHouse, see the PropertyTileList and Paginator components for an example of inter-component communication using a component event.
  • See PropertyTileList and PropertyMap for an example of using an application event to communicate between components in App Builder.

Third-party JavaScript libraries

Remove dependencies on unneeded libraries

Before you decide to use a third-party library in a Lightning component, you should reevaluate if you really need that library. DOM manipulation libraries (like jQuery) and UI libraries (like Bootstrap or jQuery UI) in particular may no longer be needed when working with the Lightning Component Framework.

DOM manipulation libraries
JavaScript has come a long way in recent years. Many DOM manipulation utilities we couldn’t live without in libraries like jQuery are now standard in the language. Modern frameworks like the Lightning Component Framework also provide abstractions that make jQuery less relevant.

UI libraries
You may also want to reconsider the use of UI libraries like Bootstrap and jQuery UI. Although these libraries provide useful components, they have their own UI identity that can clash with the Lightning Experience identity. The Base Lightning Components and the Lightning Design System offer similar capabilities while providing a consistent user experience.

MVC frameworks
At a high level, libraries like React and AngularJS have the same focus as the Lightning Component Framework: They provide code organization and utilities to create components. Using another MVC framework together with the Lightning Component Framework inside a component isn’t recommended. You can, however, use a Lightning component as a container to host components built with other frameworks (like React and AngularJS) inside the Lightning Experience, but this is outside the scope of this document. See LockerService and Lightning Container Component: Securely Using Third-Party Libraries in Lightning Components blog post for details.

Use minified versions of libraries and style sheets

When you use a third-party library, make sure you use minified versions of the library and style sheet.

More information:

Read Building Lightning Components Chart.js blog post for an example of how to use Chart.js in Lightning components.

Base Lightning Components

There are two sets of core user interface components:

  • The original components that were made available when the Lightning Component Framework first came out live in the ui namespace (<ui:button>, <ui:inputText>, and so on).
  • The new and improved components, also known as the Base Lightning Components, live in the lightning namespace (<lightning:button>, <lightning:input>, and so on).

Whenever possible, use the Base Lightning Components (components in the lightning namespace). Benefits include:

  • Styles: Base Lightning Components are styled with the native Lightning look and feel.
  • Performance: Base Lightning Components are already loaded at the client-side and don’t require additional download or processing. Our performance optimization efforts are also focused on components in the lightning namespace.
  • Innovation: The Lightning namespace is where components are being actively developed. This is where you can expect to see new and improved components moving forward.
  • Accessibility: Base Lightning Components are built for accessibility.
  • Client-side validation: Base Lightning Components include client-side validation when applicable.

Image optimization

  • When possible, use the (sprite-based) Lightning Design System icons (using <lightning:icon> and <lightning:buttonIcon>) instead of custom icons.
  • Lock image dimensions (to avoid reflows) and serve the image in those dimensions when possible. For example, don’t load a high-resolution image to display a thumbnail.

Rendering and reflow

  • Understand when and why your component is being rerendered. Read the Rendering Lifecycle and Events Fired During the Rendering Lifecycle sections in the documentation.
  • Minimize the number of times your component is being rerendered.
  • Lock DOM regions to specific dimensions to avoid browser reflows of surrounding areas.
  • Avoid direct DOM manipulation.

Development settings vs production settings

Optimizations for a production environment and a development environment are different. Here is a typical question asked in the developer forums:

“When I make changes in a component, I need to refresh the page a few times in the browser for my changes to take effect. Why do I have to do that, and how can I avoid it?”

The reason is that to optimize performance in a production environment, component definitions are cached at the client-side. As a result, when you make a change to a component, and then reload the page hosting the component, you can still get the previous version of the component served from the client cache. You can usually get the new version of the component by refreshing the page a couple of times, but that isn’t a suitable developer experience. The solution is to disable client-side caching during development.

Recommended settings:

Development Production
Debug mode On Off
Component caching Off On
  • To enable/disable debug mode: Setup > Custom Code > Lightning Components
  • To enable/disable component caching: Setup > Security > Session Settings > Caching, and uncheck Enable secure and persistent browser caching to improve performance

IMPORTANT NOTE: Client-side caching and debugging are org-wide settings. Disabling client-side caching and enabling debugging has a significant impact on performance. Only do this in your development environment, not in a production environment. Similarly, if you run tests to assess the performance of your environment, remember to turn these settings back to the production values.

Performance profiling tools

If you face a performance problem in a component, make sure you understand what the real problem is before trying to solve it. Don’t make assumptions. Use profiling tools to identify the performance bottlenecks.

Summary

The performance of an application is impacted by many different factors. The performance optimization techniques described in this article are general guidelines that should help you build applications that are faster and more responsive. Try them out in your application, and let us know what your favorite performance optimization techniques are.

Leave your comments...

Lightning Components Performance Best Practices