background

Performance optimization is a perennial topic on the front end, and we all want to deliver a better and faster user experience. There are many ways to optimize the performance of a page. This article mainly introduces Alibaba.com’s practical experience using ServiceWorker to optimize the performance.

Performance indicators

Before we talk about performance tuning, we need to take a look at how page performance is defined. In fact, there are many ways to define performance and indicators, in addition to the well-known domReady TTFB and other indicators. Google has come up with a series of performance metrics that focus on user experience

There are roughly FirstPaint (FP, First time you draw), First ContentFul Paint (FCP, First Meaningful Paint), Time to Interactive (TTI).

Meaningful in FCP is actually a relatively subjective definition. For example, we think that a search box on a search result page is not Meaningful enough when it is displayed, while a search item is Meaningful when it is loaded. This process relies heavily on thinking to define. Alibaba.com uses its own FCP as the first screen.

Chrome 78 has tried to use a generic algorithm to calculate FMP, which is based on changes in page padding to determine relatively stable points in Time. For example, Time to First Meaningful Paint: A Layout-based approach

Process analysis

Now that we know what we want to optimize, let’s take a look at what processes we need to optimize to optimize performance.


We can see that in the whole process, the time consuming of network + Server is actually much larger than the TTFB we usually see (generally TTFB is quite large). According to our previous test on a page with a ServerRT of about 200, the time consuming of network + Server is more than 800ms. ** And more importantly, this part of the time for the front end can do limited means, difficult to effectively optimize.

How to reduce network time

To minimize this time, the best we can do is to pre-load the network request on the previous page

Resource Hints

One of the most common methods of preloading is Resource Hints, which we often see in on some websites

<link rel="dns-prefetch" href="//cdn.domain.com">
Copy the code

This code tells the browser to resolve the domain name ahead of time, thus skipping the subsequent DNS resolution time.

Also, Resource Hints can be used to establish connections ahead of time (don’t underestimate this time) and to pre-cache resources.

For details, see Resource Hints. Here is not an introduction.

What about pages

Resource Hints, however, only provides DNS, connections, and Resource preloading, but does not provide page preloading & caching capabilities.

There is a Prerender tag that was intended to be used for pre-rendered pages, but has actually been deprecated by Chrome in favor of NoStatePrefetch. And it’s obviously not as “long-lasting” as resource caching.

There are several issues we need to address during page preloading before we can actually apply them to production

Fast invalidation client cache

One is the ability of the client cache to fail quickly when, for example, we fix an online bug, or actively switch page content. The client cache needs to be invalidated quickly. Instead of just waiting for it to expire.

Cache real time

In general, the content of a page is much more dynamic than static resources, and we want to keep the content of a page as up-to-date as possible without compromising cache hit ratios.

These requirements make it impractical to cache pages using HTTP caching directly

This is where ServiceWorker’s capabilities need to be introduced to address page preloading caching issues.

ServiceWorker

ServiceWorker is a JS that can be deployed in the browser and can respond to all network requests on the page by registering fetch events.

For more details, see Using ServiceWorker

this.addEventListener('fetch'.function(event) {
  event.respondWith(
    // magic goes here
  );
});
Copy the code

With this mechanism, we can load pages that need to be preloaded from the previous page, store them in a store like IndexDB, and retrieve the cached content from the ServiceWorker when the user visits the next page.

How does ServiceWorker resolve caching issues

The client cache is invalid

We can still have the ServiceWorker fetch the cached content directly (to ensure performance) and check the cache configuration on the CDN at a different location for some time to determine whether to invalidate the cache.


// sw.js this.addeventListener (' fetch', function(event) {event.respondwith (caches. Match (event.request)); });Copy the code

And in another place

setInterval(async() = > {const res = await fetch('/cdn/cache-config');
  const data = await res.json();
  caches.checkCacheLifeTime(data);
}, CHECK_CACHE_INTERVAL);
Copy the code

Cache real time

In fact, with ServiceWorker, we have rich and flexible control over the cache process. We just need to implement a mechanism to hit the cache and go online to fetch the latest page to cover the cache.

When a page request is made to the ServiceWorker, the ServiceWorker requests both the cache and the network, gives the cached content directly to the user, and then overwrites the cache.


this.addEventListener('fetch'.function(event) 
{
  fetch(event.request).then(res= > caches.update(res));
  event.respondWith(
    caches.match(event.request)
  );
});
Copy the code

What to preload

After solving the basic ability of preloading, we still have to face a problem: too many pages, one or two pages can also manually write dead preloaded content, once a long time, often many places do not know what to preload, resource version upgrade forgot to change the preloading and so on.

We can generalize preloading rules by reporting data when resources are accessed.

When a user accesses a page, the resource load (which can be obtained from performing.getentries ()) and the last page are reported. On the server side, preloaded rules can be periodically “summarized” from the data of the past period of time, and then updated to the CDN for client consumption.


SELECT COUNT(1) as role, resUrl
FROM res_log
WHERE time in LAST HOUR
AND pre_page = 'homepage'
AND hit_cache = 'N'
ORDER by role
Copy the code

What should be preloaded on a page can be concluded by directly ordering the highest number of occurrences from the reported path page => resources.

Monitoring errors

Currently, there are many error monitoring platforms in the front end, most of which are not individually adapted for ServiceWorker. In fact, ServiceWorker is just a JS execution environment, which can be used to listen for errors through onError and addEventListener.

self.onerror = function (errorMessage, scriptURI, lineNumber, columnNumber, e) {
  logError(e);
};

// Listen for exceptions for promises
window.addEventListener('unhandledrejection', (event) => {
  logError(event.reason);
  event.preventDefault();
});
Copy the code

conclusion

With DNS, connections, resources, and even page preloading capabilities, we can reduce or even eliminate the “800ms” of user blank screen in some scenarios. With data-driven capabilities, we can automate the configuration required for preloading.

For a large website, the performance optimization of landing pages is very important, but the site pages should not always be a fragmented page experience, in the appropriate actual preloading can make users have a better experience in the site.