laitimes

The road to the evolution of landing page performance

author:Flash Gene

Information flow delivery is the key channel for the company to obtain public domain traffic, and it is also the main source of external traffic for the company. The landing page will be the first step in this traffic link. Therefore, the overall loading experience and interactive experience of the landing page have become an important factor affecting user conversion.

Excerpt from Google's report on Core Web Vitals:

Why web page performance matters

Research shows that better core web motives can improve user engagement and business metrics. For example:

Research shows that when a site reaches the Core Web Vitals threshold, users are 24% less likely to abandon loading pages.

Largest Contentful Paint (LCP) 每减少 100 毫秒,Farfetch 的网站转化率就会提高 1.3%。

Many of our current landing pages are built by low-code platforms, and many performance and interaction issues have been exposed during the early launch process, such as the white screen on the homepage and the time taken to load asynchronous components.

From the perspective of the two key loading performance indicators, the median FCP of the current page is more than 1500ms, and the median LCP is more than 4000ms, while compared with the industry standard, the data that reaches the excellent level is FCP less than 1000ms and LCP is less than 2500ms, which is obviously a big gap. For landing pages, we will also aim to achieve the minimum standard to disassemble and optimize, and at the same time, we also need to optimize the details of the interaction within the page.

Let's share how the whole optimization action works.

Teardown analysis

A web page with poor performance, through the analysis of Lighthouse and Performance, combined with the loading sequence diagram, can already find 90% of the problems, here we first look at a simple sequence diagram corresponding to the current page:

The road to the evolution of landing page performance

Combined with the analysis of the entire project code (the specific code logic is scattered, I will not post it here), which is roughly summarized into the following six problem points:

1. For DNS, TCP and TLS connections, the current code does not have any optimization logic, which is promising;

2. JS/CSS static resource files have the problem that a single file is too large (more than 500KB before Gzip);

3. There is a problem of duplicate references in static resource packages;

4. Can XHR request blocking be avoided before FCP and LCP are triggered?

5. FCP can be triggered after the HTML is loaded, not after static resources and XHR requests;

6. There is still room for optimization of images and components: asynchronous components, image compression and conversion to webp, and image preloading.

Taking a conventional approach to each specific problem may yield the following approaches, such as:

1. Use SSR/SSG to solve the problem of XHR request blocking and static resource loading blocking;

2. Solve the problem of static resource files through dependency analysis and reasonable subcontracting scheme;

3. Solve the network connection time through pre-connection;

4. Solve the loading time of asynchronous components and large images by preloading.

Actual optimization process:

In addition to refactoring to SSR, other solutions are relatively cheaper and easier to implement, so we first applied a relatively more conventional solution, and after the optimization was completed, the performance of loading data was basically about to reach the goal we set, but the blocking of XHR requests and static files was still not solved, the page still had a white screen, and the user's actual experience was still defective, so we had to consider the headache of SSR refactoring......

Next, in response to the above problems, I will disassemble them one by one and analyze them in detail to see how to achieve satisfactory results in the end.

Engineering builds

The main purpose here is to address the second and third points mentioned in the teardown analysis.

1. Dependency analysis

The first thing in this part is to open the dependency analysis, and every time there is a big change, first look at the dependency analysis in the development environment, and whether the things loaded in are reasonable, so that you know in mind.

Whether it's Vite or Webpack, there are corresponding tools, such as Webpack's webpack-bundle-analyzer visualization tool, through the visual interface and the generated stats.json, we can analyze and optimize one by one.

The road to the evolution of landing page performance

2. Repeated dependencies

Here the landing page found an obvious problem through the above dependency analysis:

The dependencies of some components of the low-code platform component library all write the same dependencies, resulting in npm i when these dependencies are reinstalled and repackaged:

The road to the evolution of landing page performance

The easiest way is to remove the dependencies of the sub-component and write the dependencies in the outermost layer, of course, there are more elegant ways to deal with it, here after dealing with the duplicate dependencies, the static files in the production environment are 100KB+ less.

This is just one example, with the help of this tool, students can also find some other unknown problems more easily.

3. Reasonable subcontracting

Regarding subcontracting, the build tool will have its own set of default policies, but if you want to achieve the extreme, you must customize the configuration for specific business scenarios.

For the landing page, I formulated a subcontracting principle, and then made a detailed configuration according to this principle:

Front-end framework - UI library - core tool library - synchronous component - asynchronous component - follow the default policy

The detailed configuration will not be posted here, taking Webpack as an example, it is to use splitChunks with the regular to configure the partitioning parameters in advance, and the partitioning of asynchronous components is implemented with the import() method, which will be introduced in detail later.

The following figure shows the static file list of npm run build after the detailed subpacking:

The road to the evolution of landing page performance

In fact, most of the files in this list are loaded on demand based on page interaction (i.e., the asynchronous component segmentation mentioned above), ensuring that only the necessary content is loaded on the homepage. At the same time, a single large file (a single file exceeding 500KB) that existed before is also split into multiple small files, and the largest file is no more than 100KB after Gzip.

According to the browser's concurrency limit of 6 TCP connections, you may think that this is a negative optimization in the HTTP 1.1 era, because CSS Sprite graphs were still popular at that time, and it was also to reduce the number of concurrent connections. However, the advantages of HTTP2.0 can be better played in this case, compared with the actual experiment on the landing page, the normal network environment loading a single large file (about 800KB) is about 5% slower than loading 5 small files, if it is a weak network environment, the gap will be more obvious.

More importantly, a single large file is not conducive to cache management, and some modules that have not been touched all year round, such as the front-end framework, UI library, and core tool library in the above subcontracting principle, are all iteration with a very low iteration frequency, and can effectively use the cache by detaching them separately.

If you feel that your page loading performance is not good, you can also try to learn from the above ideas to optimize.

Pre-connected, pre-loaded

Points 1 and 6 of the teardown analysis will be optimized in the following actions.

1. Pre-connection

As the name suggests, a pre-connection is a pre-established network connection, including DNS, TCP, TLS, and when the domain name is subsequently requested, the established connection can be directly used. Therefore, it needs to be used on key nodes, such as: JS file domain name, main image domain name, and important third-party SDKs.

The currently available methods are:

  • dns-prefetch,预先对指定域名进行DNS解析。
  • preconnect,预先建立所有连接,包括DNS、TCP、TLS。

Both of these are HTML features, and the landing page is used together to provide degradation with dns-prefetch when preconnect is not supported:

The road to the evolution of landing page performance
The road to the evolution of landing page performance

In the actual Connection View of the landing page in the above figure, you can intuitively see that under the effect of preconnect, the TCP and TLS connections have been established in advance before the actual resource request.

This time will be saved directly, and this is still in the case of DNS caching, if the new user does not have a DNS caching, then the time savings due to DNS latency will be even more.

2. Preloading

Pre-connection is to establish a connection but not load, while pre-loading is to load but not execute, and the prefetch and preload features are mainly used at present.

2.1、prefetch、preload

- preload prompts the browser to immediately load static resources and cache them with a higher priority, so that there is no need to wait for subsequent use, and the priority is marked as High in the browser

- prefetch prompts the browser to use a resource that may be used in the future, and the browser will load the corresponding resource and cache it when it is idle, and the priority in the browser is marked as Lowest, which is the lowest priority

It can be seen that preload is suitable for the resources that need to be used immediately, or the important resources in the current page, in order to improve their loading priority, in the landing page, before the body tag, we set the preload of the JS files needed for the home page, so that before the body tag starts to render, the JS has begun to load, and at the same time does not block the resolution execution of the subsequent tags, which is a bit similar to the defer attribute of the script tag.

The road to the evolution of landing page performance
The road to the evolution of landing page performance

In this diagram, you can see the difference between using and not using "Extract Priority", using preload and setting the priority to High, reducing the LCP time from 2.6 seconds to 1.9 seconds.

The road to the evolution of landing page performance

The above is all about preload, so where is the application scenario of prefetch? Let's see how it works with asynchronous components.

2.2. How to apply preloading to asynchronous components

The road to the evolution of landing page performance

First, the component is split into a separate JS file named "CmsLayerGradeTabSelector" when packed.

The webpackPrefetch annotation above will be translated by webpack into the corresponding rel="prefetch" link tag. This means that the file will be loaded and cached by the browser when it is idle, and when the interaction triggers an asynchronous component, it will go directly to the cache to retrieve the file.

If you don't add webpackPrefetch, the JS file will be loaded on demand, that is, it will be loaded when the interaction is triggered, which will bring the interaction experience problem.

2.3. Precise control of loading timing

The road to the evolution of landing page performance

Through the above code, in the actual scenario, we can control the preloading and execution of a resource at any time. For example, if you know that a video will be played or a large image will be rendered, you can preload them.

Problems raised by low-code platforms

After all the above steps are optimized, the overall effect is also more obvious, and the measured FCP and LCP on the line are relatively close to the set goal: FCP is lower than 1000ms, LCP is less than 2500ms, but because the blocking of XHR and static resources is still not solved, it is also mentioned at the beginning of the article, which will lead to a white screen, and the user will not feel good, if this can be solved, the overall performance will go to a higher level.

Finally, for points 4 and 5, we found that the problem is actually caused by the low-code platform, which directly stores the content to the backend after the code is released, and the front-end needs to obtain the page content and business data in real time according to the corresponding page ID. This will directly lead to a white screen on the homepage, because even after the static resource is fully loaded, you still need to call the API to get the page data before you can start rendering the page.

After analysis, in fact, if the page data can be static instead of being pulled instantly, the problem can be completely solved.

SSR

The first thing that comes to mind may be to refactor the page from CSR to SSR, which can completely solve the problem of static resources and XHR requests, but because the current page relies on a low-code platform, it has been iterated many times. The refactoring will involve the entire B-side, the logic of the low-code platform is complex, and the development cost of the evaluation is high. At the same time, the use of SSR for high-traffic delivery pages has high server requirements, and it may require a large number of stacked machines to ensure performance, which will increase the expenditure.

Data is static

Taking a step back, combined with the advantages of SSR, if you can put the page data related to the XHR request in HTML in advance, and go straight out through HTML, you can get the page data and structure as quickly as possible, compared to SSR, there is only more static resource loading, and before static resource loading, we can also do a lot of things.

How are pages static?

1. Static transformation

A competent low-code platform can do just that, starting with a look at the page generation process without static:

The road to the evolution of landing page performance

As can be seen from this figure, the C side can only start the rendering action at the moment when it gets the JSON Schema through the XHR request, and what needs to be done here is to make the JSON Schema data stored in static form in HTML. There are a few things that can be done on Nginx and the server side to implement this idea:

The road to the evolution of landing page performance

In contrast, the process is much more complicated:

Every time the B side publishes a page, the server will take out the original HTML from the Qingzhou container, embed the JSON schema into the HTML through script tags, and then store the corresponding projectId on the tag in OSS.

The C-end HTML request will be forwarded to the backend service by Nginx, and the backend will take out the corresponding spliced HTML and return it to the frontend.

The road to the evolution of landing page performance

At this point, we have achieved data static, thus saving the two time-consuming steps of static resource loading and XHR request to obtain JSON Schema, and get the page data in advance, draw a simple waterfall to see the difference:

The road to the evolution of landing page performance

Once static, First Contentful Paint happens as soon as the HTML is loaded, and is a valid content draw because we already know the content of the page.

2. Draw in advance

Continuing from the previous section, in the case of knowing the page structure and data, how to render the necessary content faster and more completely, in order to retain users, at this stage we do not have JS and CSS, so we can only write simple logic in the Body to process the JSON Schema and show the key content:

The road to the evolution of landing page performance
The road to the evolution of landing page performance

Through the above processing, with the help of the base64 format header image in the page data, the key content of the page header, course information, and detail map can be displayed almost immediately, and the user can already browse at this time, and the remaining complete content only needs to wait for the static file to load.

After processing, the page performance is cleared, and the visual experience is almost open in seconds in the normal wireless network environment:

The road to the evolution of landing page performance

If we want to show more content at this time, we are also free to expand it, and it is very flexible here.

The 5th and 6th points in the dismantling analysis have been completely solved here, so let's summarize:

For a mature platform, the static processing of the page is definitely a must, which can be a variety of other processing forms, drawing on the advantages of various rendering solutions such as SSR, SSG, ISR, etc., to maximize the fit for your own business.

In fact, this idea is also suitable for some small sites, because the transformation cost is not high: you can make good use of OSS, modify the back-end storage logic, and add an interface.

At last

After these optimizations, the median online FCP was shortened from 1500ms to 500ms+, and the LCP was shortened from 4000ms+ to 2000ms.

The optimization has been running stably for a period of time, and there is still one point that can be optimized for the current situation:

At present, for the static HTML of the landing page, due to the consideration of page version control, caching problems, and the different domain name requirements of online businesses for each business line, Nginx forwards it to the backend interface and then goes to OSS to obtain it. In this way, the time loss caused by the two-layer processing of Nginx and back-end services is saved.

In fact, at the beginning of the project, we should consider the general code organization structure, the routine and unconventional optimization solutions that can be achieved in advance, so as to avoid analyzing and optimizing in the later stage and bringing higher costs.

Author: Hu Bin

Source-WeChat public account: Gaotu Technology

Source: https://mp.weixin.qq.com/s/yjEnfe_RTTAMP2bAaAXLxA

Read on