Every word we say, the first person to hear is ourselves, negative words many, their own believe.

concise

  1. RUM can well track various operations of users in the web page and provide real-time loading data of the website
  2. Rendering first screen content first improves Frist Meaningful Paint (FMP) and Time to Interactive (TTL) performance metrics
  3. Latency is the bottleneck. What’s the fastest way to transmit nothing
  4. Use WebSocket for real-time applications
  5. A source is defined by application protocol, domain name, and port

The article summary

  1. Monitor SPA performance
  2. Improve SPA Performance (6 types)
    1. Delay rendering the content below the first screen
    2. Lazy loading of non-essential data
    3. Caching static content
    4. Use WebSocket for real-time applications
    5. Use JSONP/CORS to bypass the same origin policy
    6. CDN processing

1. Monitor SPA performance

There are many tools available to help you monitor the performance of your SPA. First, you can use Chrome’s built-in Devtool or a specific plug-in.

  1. Lighthouse: An open source automation tool for improving the quality of web applications
  2. Ember Inspector: A plug-in for the ember.js project
  3. React Performance Devtools: optimized plugin for React. Js projects

The downside of these tools is that they do not accurately measure the loading speed of SPA applications.

In order to really measure the true load Speed of SPA, there are some sub-tools in Chrome (such as Speed Index) that simulate the actual surfing process of the user. Here’s an article about Speed Index.

However, real user operations are affected by a variety of devices and networks, making it difficult to simulate with a single plug-in and tool.

Therefore, we can simulate Real User Monitoring (RUM) using Real users for application processing. It can well track the various operations of users in the web page and provide real-time loading data of the website.

Here is a list of RUM tools for SPA (some addresses, 🪜 required)

  1. Dynatrace
  2. Catchpoint
  3. Akamai mPulse
  4. Appdynamics
  5. Raygun Real User Monitoring
  6. Sematext Experience
  7. New Relic Browser

In the process of RUM processing, we need to be able to distinguish and identify the page navigation stage and page loading completion stage

Page navigation stage: the stage that occurs during the browser page loading process. 1. After the application is loaded. The user clicks on a link or button that loads a new page.

There are many ways to distinguish between the two stages:

  • Use the Resource Timing API to identify when an AJAX is triggered, and thus know exactly when the page navigation occurs

  • The Mutation Observer can be used to detect when DOM elements have been modified and the end time of the interface can be determined using the Resource Timing API.

Practice has proved that the above solution scheme can not provide accurate results. For example, even if no new page is loaded, data can be fetched via AJAX in a SPA page. Or the network request may lose data due to some reason in the transmission path, but it is not affected by network fluctuation in the page.

We can use a simple API to detect page load time information.

var rumObj = new RumTracking({
  'web-ui-framework': 'EMBER'
});
 
/ / App is loaded - window. Performance. Timing. NavigationStart is a sign of a loading start
rumObj.setPageKey('feed_page_key');
// Do page rendering
rumObj.appRenderComplete();
 
// Page navigation time monitor
rumObj.appTransitionStart();
rumObj.setPageKey('profile_page_key');
rumObj.appRenderComplete();
Copy the code

The above approach counts page load information, but requires each page to write the specified injection logic.

Many SPA JS frameworks have a specific lifecycle, and we can use this mechanism to add the inspection code described above.

// Add the listening logic at the beginning of the page navigation
router.on('willTransition'.() = > {
  a.rumObj.appTransitionStart();
});
 
// Add listening logic at the end of the page lifecycle
router.on('didTransition'.() = > {
  Ember.run.scheduleOnce('afterRender'.() = > {
    a.rumObj.appRenderComplete();
     });
});
Copy the code

The developer can monitor when the page Navigation starts by specifying navigationStart in Navigation. The event of routing willTransition that will occur in the page navigation is triggered.

By listening for the didTransition event and adding a callback to the afterRender queue, we can tell when the page is fully loaded in both modes.

Now we can get the various times when the page loads. In order to better find the bottleneck of page loading, we need to use RUM data for analysis and processing.


2. Improve SPA performance (6 kinds)

2.1 Delay rendering the content under the first screen

That is, render the page information on the first screen first.

If your SPA takes a lot of time in the render phase, then lazy rendering for non-first screen pages is a step that should not be ignored. In the rendering phase, the HTML parser converts all the HTML in the page into DOM objects and generates the corresponding DOM tree.

Since HTML parsing is early in the main thread of the browser, building too many DOM(where all elements of the current page are parsed) blocks the main thread of the browser. The application then takes too long to load.

Another way to speed up rendering is to give each component a different rendering priority.

High priority (green box): Always rendered. The elements of this layer are all components in visual range. Subpriority (yellow): Components of this layer are rendered incrementally. Low priority (red): Components that are out of visual range are rendered only when the user scrolls the page to where they are

This treatment can improve Frist Meaningful Paint (FMP) metrics. (This reduces the time the user can see the core content of the page).

Time to Interactive (TTL) performance metrics can also be improved by filtering rendering (not rendering) of invisible elements.


2.2 Lazy loading of non-essential data

After optimizing the performance of the rendering phase, we continued down the rendering pipeline. Performance bottlenecks may also be found during the transition phase.

At this stage, the SPA loads and normalizes the data, and then stores the processed data into memory. To optimize this phase, reducing the amount of data is a good optimization solution.

You can use a high-priority call to get the data required by First Meaningful Paint, and another callback to lazily load the rest of the data required by the page.

The above approaches work for both startup mode and in-page navigation because they reduce front-end time.

Some SPA frameworks, such as React/Vue, allow developers to split application code into many bundles. As a result, you can load on demand or delay processing of non-essential bundles. This method can speed up the first navigation. For example, you can load only the parts that the user can access immediately and defer everything else (such as the parts that need authorization).


2.3 Caching static Content

Review your SPA to identify images or other static resources that can be cached on a user’s device.

The time it takes to get data from memory or Web Storage is much less than the time it takes to request data through HTTP.

Delay is the bottleneck, and the fastest speed is to transmit nothing.

Device memory is faster than the fastest network requests, so caching is a necessary tool for optimization.

For large collections, you can use some type of paging and rely on the server for persistence, or you can write an LRU algorithm to remove redundant items from storage.

Or use Service Workers to cache static content in the SPA.

It runs in the backgroundClient script. You can use them to reduce traffic and enable offline functionality. When the browser requests content, it first goes through the service worker. If the requested content exists in the cache, the service worker retrieves it and displays it on the screen. In other cases, it will request resources from the network.

You can use the IndexedDB API to cache large amounts of structured data.


2.4 Use WebSocket for real-time applications

WebSocket can realize bidirectional, message-based text or binary data transmission between client and server. It is the closest API to a socket in the browser.

Unlike HTTP, the client does not have to constantly send requests to the server to get new messages. Instead, the browser simply listens to the server and receives the message when it is ready.


2.5 Using JSONP/CORS to Bypass the Same Origin Policy

Most applications require data from third parties.

However, due to the same-origin policy, you cannot make AJAX calls to non-same-origin third-party services.

A source is defined by application protocol, domain name, and port. For example, (HTTP, wl.com, 80) and (HTTPS, wl.com, 443) are different sources

The starting point for the same-origin policy is simple: browsers store user data, such as authentication tokens, cookies, and other private metadata, that cannot be disclosed to other applications. If there is no same-origin sandbox, the script in Wl.com can access and manipulate the Resident data from the resident record of Resident record department.

In order to be able to access third-party websites, the application needs to use Origin Server as a proxy.

The extra round trips mean more delays.

If the retrieved data is not processed or stored in the system, resources can be requested directly. To do this, you can use JSONP or Cross-source Resource sharing (CORS) for data capture.

JSONP

The first step

Add a

<script src="http://api.foo.com?callback=bar"></script>
Copy the code

The requested script url has a callback argument (? Callback =bar), which tells the server the name of the client’s callback function (bar).

The second step

The server receives the request, concatenates a string, places the JSON data inside the function name, and returns it as a string (bar({… }))

The third step

The client parses the string returned by the server as code because the browser thinks it is the script requested by the

Dynamic send

Web pages dynamically insert

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute('type'.'text/javascript');
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function () {
  addScriptTag('http://example.com/ip?callback=foo');
}

function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};
Copy the code

The code above makes a request to server example.com by dynamically adding

Note that the query string for this request has a callback parameter that specifies the name of the callback function, which is required for JSONP.

When the server receives the request, it returns the data as an argument to the callback function.

Foo ({' IP ':' 8.8.8.8 '});Copy the code

JSONP can only be GET requests

In the meantime, we can use the async and defer properties to optimize

attribute explain
There is no defer or async The browser loads and executes the specified script immediately, “immediately” before rendering the document element under the script tag, meaning that it is loaded and executed without waiting for subsequent document elements to be loaded
async Loading and rendering of subsequent document elements takes place in parallel with the loading and execution of script.js (asynchronously)
defer The loading of subsequent document elements is done in parallel (asynchronously) with the loading of script.js, but execution of script.js is completed after all elements have been parsed and before the DOMContentLoaded event is fired

CORS

CORS stands for Cross-Origin Resource Sharing. It is a W3C standard and a fundamental solution for cross-source AJAX requests.

However, requests using any method other than GET, HEAD, and POST issue a Preflight check to confirm that the server is ready for cross-source requests.

To make a precheck request, the client sends another request describing the source, method, and header of the cross-source AJAX call. Based on this information, the server decides whether to process the call. After receiving the response, the client sends a request to the third-party resource.

=> Prepare the request OPTIONS/resource-js HTTP/1.1Host: thirdparty.com
     Origin: http://example.comAccess-Control-Request-Method: POST Access-Control-Request-Headers: My-Custom-Header ... <= Prepare the response HTTP/1.1 200OK (2) Access - Control - Allow - Origin: HTTP://example.comAccess-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: My-Custom-Header ... (formal HTTP request) ③Copy the code
  • ① Verify the pre-options request for permission
  • ② Successful preparatory responses from third-party sources
  • ③ The actual CORS request

Precheck requests for one more round trip time, virtually increase the request delay time.


2.6 the CDN processing

CDN is an acronym for Content Delivery Networks, which deliver Content closer to users and make the online experience faster and more reliable.

A content delivery network (CDN) is a geographically distributed set of servers that accelerates the delivery of Web content by keeping it closer to users. Data centers around the world use caching, a process of temporarily storing copies of files that allows you to access Internet content more quickly using web-enabled devices or browsers from servers closer to your location.

CDN CDN caches content such as web pages, images and videos in proxy servers near your physical location.

Think of the CDN as an ATM machine. There are cash machines on almost every street corner these days, allowing us to withdraw cash quickly and efficiently. Instead of waiting in long lines at the bank, you can find ATMs at many convenient locations and withdraw cash quickly

Using CDN for SPA means faster script loading and reduced interaction time


Data reference

  1. How to speed-up your Single-Page Application
  2. Service Workers
  3. The definitive guide to Web performance optimization
  4. What is a CDN