Performance: Speed Up Your App

Performance: Speed Up Your App

If a web app has performance issues, finding the cause can be both a time-consuming and nerve-consuming task. To help you avoid and solve performance issues in your app, here are some good practices we've discovered while dealing with SAPUI5 apps.

SAPUI5 apps are basically JavaScript files sent to a client by a server and interpreted by the browser. So it's not only the coding of the app that can cause slow performance. It often turns out, for example, that the configuration is wrong. Slow networks or servers may also have a heavy impact on the performance of a web app. Let's have a look at the most common issues that impact performance.

Enable Asynchronous Loading in the Bootstrap

Configuration issues are often caused by an old bootstrap or a wrong usage of the activated features. Here's an example of what a bootstrap should look like for an up-to-date SAPUI5 app:

<script 
	id="sap-ui-bootstrap"
	src="/resources/sap-ui-core.js"
	data-sap-ui-theme="sap_belize"
	data-sap-ui-compatVersion="edge"
	data-sap-ui-async="true"
	data-sap-ui-onInit="module:my/app/main"
	data-sap-ui-resourceroots='{"my.app": "./"}'
>

The most important setting is data-sap-ui-async="true". It enables the runtime to load all the modules and preload files for declared libraries asynchronously, if an asynchronous API is used. Setting async=true leverages the browser's capabilities to execute multiple requests in parallel, without blocking the UI.

The attribute data-sap-ui-onInit defines the module my.app.Main, which will be loaded initially.

Note

Configuration of the bootstrap can only be done for standalone applications and when the bootstrap is under control of the developer. The bootstrap of applications from a Fiori Launchpad is managed by the Launchpad.

Note

The data-sap-ui-async="true" configuration option requires extensive testing as well as cooperation on the application side to ensure a stable and fully working application. It is, therefore, not activated automatically, but needs to be configured accordingly. If you encounter issues or want to prepare your application for asynchronous loading, see Is Your Application Ready for Asynchronous Loading? The bootstrap attribute data-sap-ui-async="true" affects both modules and preload files. If it is not possible to load the modules asynchronously (e.g. for compatibility reasons), use data-sap-ui-preload="async" to configure at least the preloads for asynchronous loading. For further information, see Standard Variant for Bootstrapping.

If you listen to the init event as part of your index.html page, make sure that you implement the asynchronous behavior also here, as shown in the following code snippet:

<script>
	sap.ui.getCore().attachInit(function() {
		sap.ui.require(["sap/ui/core/ComponentContainer"], function(ComponentContainer) {
			new ComponentContainer({
				name: "your.component",
				manifest: true,
				height: "100%",
				componentCreated: function(oParams) {
					var oComponent = oParams.getParameter("component");
					// do something with the component instance
				}
			}).placeAt("content");
		});
	});
</script>
Note

Please note that this variant with inline scripting is not CSP-compliant. It is better to create a module with sap.ui.define which contains the startup code and load it via data-sap-ui-onInit="module:my/app/main" ( this usually also requires a declaration of data-sap-ui-resourceroots, e.g.: data-sap-ui-resourceroots='{"my.app": "./"} ).

Note

Applications without a descriptor file can declare additional dependencies explicitly via the bootstrap parameter data-sap-ui-libs. If those dependencies are not listed, such as transitive dependencies that are inherited from a listed library, SAPUI5 will load them automatically, but then has to first read the configured libraries and find out about these dependencies. This can take time as the application might benefit less from parallel loading.

Additional Information:

Ensure that Root View and Routing are Configured to Load Targets Asynchronously

Please check the rootView of the application's manifest.json file for an async=true parameter. This allows the root view to be loaded asynchronously.

To configure the targets for asynchronous loading, please also check the Routing Configuration for the async=true parameter.

"sap.ui5": {
	"rootView": {
        "viewName": "sap.ui.demo.walkthrough.view.App",
        "type": "XML",
        "id": "app",
         "async": true
    },
    "routing": {
        "config": {
            "routerClass": "sap.m.routing.Router",
            "viewType": "XML",
            "viewPath": "sap.ui.demo.walkthrough.view",
            "controlId": "app",
            "controlAggregation": "pages",
            "async": true
        }
    },
...

Additional Information:

Make Use of Asynchronous Module Loading (AMD Style)

If modules follow the Asynchronous Module Definition (AMD) standard and the bootstrap flag data-sap-ui-async is set to true, custom scripts and other modules can also be loaded asynchronously when a preload is not available. It will help you in the future to enable asynchronous loading of individual modules combined with the usage of HTTP/2 or AMD-based module bundlers. It also ensures proper dependency tracking between modules.

But it isn't enough to write AMD modules. You also need to prevent access to SAPUI5 classes via global names. For instance, do not use global namespaces like new sap.m.Button() but require the Button and call its constructor via the local AMD reference instead.

For more information, see the API Reference: sap.ui.define.

Always avoid usages of sap.ui.requireSync and jQuery.sap.require ! In order to enable modules to load asynchronously, use sap.ui.define to create modules (e.g. controllers or components) or sap.ui.require in other cases.

Please follow the Best Practices for Loading Modules.

Use manifest.json Instead of the Bootstrap to Define Dependencies

Don't specify a link to the CSS in the bootstrap of your app; use the manifest.json descriptor file instead.

Please use the manifest.json application descriptor file to declare dependencies. This has several advantages:

  • In the manifest, the dependency information is reusable; it works when the app runs standalone and when it is embedded in the Fiori Launchpad or some other launcher.
  • Moving the dependencies to the manifest loads them later and can therefore make the first rendering happen earlier. Obviously, that first rendering cannot come from the component then.
  • Design-time tools or runtime back-end services (e.g. AppIndex in ABAP systems) can use the manifest entries to determine the transitive closure of dependencies and thereby further optimise the parallel loading of dependencies. If the dependencies are maintained in the bootstrap, developers can do this by hand, but will have to update the information on each version upgrade.

Make sure that you don't load too many dependencies. In most apps it's enough to load the libraries sap.ui.core and sap.m by default, and add additional libraries only when needed.

If you want to make additional libraries generally known in your app, without directly loading them during the app start, you can add them to the dependency declaration in the manifest.json file with the lazy loading option. This makes sure that the libraries are only loaded when they are needed:

"sap.ui5": {
	"dependencies": {
		"minUI5Version": "1.70.0",
		"libs": {
			"sap.ui.core": {},
			"sap.m": {},
			"sap.ui.layout": {
				"lazy": true
			}
		},
...

If a library preload contains reuse components and this preload is configured to be loaded lazily (via "lazy": true in the dependencies of the manifest.json), the library is not available upon creation of the related component.

In this case you need to use sap.ui.getCore().loadLibrary("my.library") before creating the component (e.g with Component.create({ name: "my.component" }) or component usage myComponent.createComponent("myUsage") ).

An indicator that a component is inside a library is the existence of an entry sap.app/embeddedBy in its manifest.json file.

Additional Information:

Load SAPUI5 from the Content Delivery Network (CDN)

In order to ensure that all static SAPUI5 resources are served with the lowest possible latency in SAP Cloud Platform scenarios, you can load the resources from the Content Delivery Network (CDN) cached by AKAMAI. Especially when running your app in the cloud, you benefit from the global distribution of servers. For other scenarios, it is possible to configure a custom CDN of choice as an external location.

Additional Information:

Ensure that all Resources are Properly Configured to Avoid 404 Errors

Languages can be configured in your manifest since UI5 version 1.77.

The manifest configuration for i18n has now the option to provide the supportedLocales and the fallbackLocale:

  • The supportedLocales should contain all languages for which you have i18n files. e.g. a file named i18n_en.properties has the locale en.
  • The fallbackLocale is the locale loaded before falling back to the root bundle.

Example: If the following language files exist:

  • i18n_en.properties (English version, "en")
  • i18n_de.properties (German version, "de")

they can be configured in your manifest.json in Section sap.ui5 under models:

"sap.ui5": {
	"models": {
		"i18n": {
			"type": "sap.ui.model.resource.ResourceModel",
			"settings": {
				"bundleName": "sap.ui.demo.todo.i18n.i18n",
				"supportedLocales": ["en", "de"],
				"fallbackLocale": "en"
			}
		}
	}
}

With AppDescriptor version 1.21.0 this is also possible in the i18n section of sap.app in your manifest.json:

"sap.app": {
	"i18n": {
		"bundleUrl": "i18n/i18n.properties",
		"supportedLocales": ["en", "de"],
		"fallbackLocale": "en"
	}
}

For more informations, see:

Use "manifest first" to Load the Component

Load the manifest.json descriptor file of the component first to analyze and preload the dependencies when loading the component. For more information, see Manifest First Function.

// "Component" required from module "sap/ui/core/Component"
// load manifest.json from default location and evaluate it before creating an instance of the component 
Component.create({
  name: "my.component",
});

Ensure that Library Preloads are Enabled

If the library preloads are disabled or not found, every module is loaded separately by an own request. Depending on the server and network infrastructure, this can take a lot of time. Except for debugging reasons, it is always recommended to make sure library preloads are used. Fortunately, the library preloads are active by default if the files are present.

In some cases it may happen that preloads are disabled:

  • The data-sap-ui-preload bootstrap attribute is empty or set to an invalid value. The attribute is optional and only necessary if the loading behavior (sync / async) needs to be overwritten manually.

  • Debug sources are enabled in the bootstrap (data-sap-ui-debug=true) or via the URL (sap-ui-debug=true).

Ensure that Application Resources are Loaded as Component Preload

Application modules (e.g. components, controllers, views or resource bundles) should be loaded asynchronously via the component preload file. Check (e.g. via the Network tab in the Google Chrome developer tools) if a component preload (Component-preload.js) is missing. If the application is not configured to load modules asynchronously, required application files may be loaded synchronously.

Note

If a component preload does not exist yet, the bundle needs to be created. For example, you may use the UI5 Tooling.

Check the Network Requests

To quickly check the network load caused by your app, look at your browser's developer tools, for example the Network tab in the Google Chrome developer tools (F12). You'll see an overview of all requests being sent. Possible issues here may be:

Synchronous requests that block each other

In this case, use the data-sap-ui-async="true" setting in the bootstrap.

Too many requests

You can use the UI5 Tooling to bundle and minimize all relevant component files by creating a component-preload file.

If you're using apps with grunt as a web server, you can use the openui5_preload task; for more information see Optimizing OpenUI5/SAPUI5 Apps in the SAPUI5 Developer Center on SAP SCN.

If you're using SAP Web IDE, refer to Application Build in the SAP Web IDE documentation.

Back-end related performance issues

  • Slow database service (e.g. OData)

  • Slow web server or CDN issues (e.g. serving of static resources)
  • Slow network infrastructure (e.g. mobile network)
  • The h2 protocol is not supported (only HTTP/1.1); ideally, the h2 protocol should be supported by the web server

Additional Information:

  • To determine the minimum required bandwidth when using UI5-based applications, you can find further information in SAP Note 2240690 on front-end network bandwidth sizing.

Migrate jquery.sap.* Modules to their Modularised Variants

Since UI5 version 1.58, the global jquery.sap.* modules are deprecated. Please use the modularised variant of the module. If you are still using the jquery.sap.* variants, a so-called "stubbing layer" may load the old module synchronously!

You can find a list of modules in the Legacy jQuery.sap Replacement documentation.

The usages can either be replaced manually or by the UI5 Migration Tool.

Note

Please make sure to declare the required modules in sap.ui.define or sap.ui.require to ensure that they get loaded asynchronously.

Migrate Synchronous Variants of UI5 Factories to Asynchronous Variants

Check if the application uses synchronous UI5 factories. Many asynchronous variants are available, e.g. for Components, Resource Bundles, Controllers, Views and Fragments. Please visit the following overview:Legacy Factories Replacement.

Use the OData Model Preload

Components can preload models for which modules are already loaded; otherwise a warning will be shown. The OData model (V2 or V4) benefits especially, because the metadata can be loaded in parallel during a component load.

"sap.ui5": {
  ...
  "models": {
      "mymodel": {
          "preload": true,
...

For the OData V2 model, also consider using the model parameter earlyTokenRequest. For more information, see the API Reference: sap.ui.model.odata.v2.ODataModel.

For the OData V4 model, also consider using the model parameter earlyRequests. For more information, see the API Reference: sap.ui.model.odata.v4.ODataModel.

For more information, see Manifest Model Preload.

Use OData Metadata Caching

To ensure fast loading times for SAP Fiori applications started from the SAP Fiori launchpad, the OData metadata is cached on the web browser using cache tokens. The tokens are added with the parameter sap-context-token to the URL of metadata requests. Please check via the developer tools of your browser (e.g. the Network tab in the Google Chrome developer tools) if the token has been appended to the request URL.

Note

This feature is currently only supported for ABAP back ends.

Additional Information:

Check Lists and Tables

The performance limits are reached differently depending on the used browser, operating system and hardware. Therefore, it is important to be mindful about the amount of controls and data bindings. This applies especially to lists and their variants (e.g. sap.m.Table or sap.ui.table.Table):

  • If a table needs to display more than 100 rows, please use sap.ui.table.Table instead of sap.m.Table The reason for this is that sap.m.Table keeps every loaded row in memory, even if not visible after scrolling. To choose the right table variant for your requirements, check out the documentation about Tables: Which One Should I Choose?
  • If the table rows contain multiple controls and/or custom-data fields, please check if they are required, or if another control can replace them. For example, another list like a ComboBox inside of a table cell may create many controls for every row, which can be very expensive.
  • Check tables for hidden columns and load only the visible ones, if possible.

Additional Information:

Further Code Optimization

You can further optimize your code by doing the following:

  • Use asynchronous view loading as described here: Instantiating Views.

  • Use the OData V4 model, which has an improved performance over the OData V2 model.

    Visit the OData V4 Model documentation and ensure that all required features are available.

    For a quick start, follow the OData V4 tutorial.

  • If you use data binding with an OData V2 service as a back end, you should consider switching your OData model to our more updated OData V2 model. For more information, see OData V2 Model.

  • Optimize dependent bindings as described here: Optimizing Dependent Bindings.

  • Avoid the usage of setTimeout() calls with values greater than 0. This usually indicates an anti-pattern in application code that is used as a workaround and should be avoided. For more information, see also JavaScript Code Issues: Don't use timeouts.

  • Don't use visibility for lazy instantiation. For more information, see Performance Issues: Don't use visibility for lazy instantiation.

  • Please ensure the application does not block the rendering while waiting for back-end requests to respond. Waiting for data before rendering anything is not the favored user experience. It is recommended to load data asynchronously and already render the page while the request is pending. Mostly, the requests won't fail, and if they do, it is better to show an error or to navigate to an error page.
  • If an XML Preprocessor is used, we recommend to use the XML View Cache. If configured in the XML View and with a properly implemented key provider (for invalidation), it is able to cache already processed XML View Preprocessor results.