preface

Front-end technology has evolved very rapidly since the creation of JavaScript. White screen optimization of mobile terminal is an important optimization direction of front-end interface experience. SSR, CSR, pre-rendering and other technologies were born in Web front-end. In the front-end technology system of MeituanPay, pre-rendering is used to improve the optimization of the first frame of the web page, thus optimizing the problem of white screen, improving user experience and forming best practices.

In the field of front-end rendering, there are several options:

CSR pre-rendered SSR homogeneous
advantages
  • Data independent
  • FP time fastest
  • The client user experience is good
  • Memory data sharing
  • Data independent
  • FCP time is faster than CSR
  • The client user experience is good
  • Memory data sharing
  • SEO friendly
  • First screen performance is high, FMP is faster than CSR and pre-render
  • SEO friendly
  • First screen performance is high, FMP is faster than CSR and pre-render
  • The client user experience is good
  • Memory data sharing
  • Client and server code public, high development efficiency
  • disadvantages
  • SEO is not friendly
  • FCP, slow FMP
  • SEO is not friendly
  • FMP was slow
  • Data sharing on clients is expensive
  • Template maintenance costs are high
  • Node is prone to performance bottlenecks
  • By comparison, the advantages of the isomorphic scheme set CSR and SSR can be applied to most business scenarios. However, in the homogeneous system architecture, the middle layer of nodes connecting the front and back ends is the core link, so the bottleneck of system availability depends on Node. Once the Node as the short board is suspended, the whole service is unavailable.

    Combined with the payment business scenario that our team is responsible for, as the payment business pursues the ultimate system stability, service unusability will directly affect the customer complaint and loss, so we adopt the browser-side rendering architecture. On the premise of ensuring the stability of the system, user experience also needs to be guaranteed, so the pre-rendering method is adopted.

    So what exactly is pre-render? What is FCP/FMP? Let’s start with the most common CSR.

    Taking Vue as an example, common CSR forms are as follows:

    Everything looks good. However, as our primary goal is user experience, we found an experience problem: the first screen is white.

    Why is the first screen white

    Browser rendering includes HTML parsing, DOM tree building, CSSOM building, JavaScript parsing, layout, drawing, and more, as you can see below:

    To understand why there are white screens, you need to use this theoretical basis for a concrete analysis of a real project. Analysis with DevTools:

    • Wait for the HTML document to return, at which point in the white screen state.
    • Render the first screen after parsing the HTML document, because the project is on

      A gray background color has been added, so a gray screen appears.

    • During file loading and JS parsing, the interface remains in gray screen for a long time.
    • When the Vue instance triggers Mounted, the interface displays the general frame.
    • The final page content is displayed only after the API is invoked to obtain the timing business data.

    Therefore, it is concluded that due to waiting for file loading, CSSOM building, JS parsing and other processes, which are time-consuming, the user will be in the first non-interactive gray screen state for a long time, which gives the user a feeling of “slow” web page. So what happens when a web page is too “slow”?

    The effect of “slow”

    According to the report on Global Web Performance Matters for Ecommerce,

    • Fifty-seven percent of users were more interested in loading a web page in less than three seconds.
    • 52% of online users believe that the speed at which a web page opens affects their loyalty to a website.
    • Every second slower results in an 11% drop in page PV and a 16% drop in user satisfaction.
    • Nearly half of mobile users give up because they haven’t opened the page within 10 seconds.

    Our team is mainly responsible for the related business of Meituan Payment. If the website is too slow, the payment experience of users will be affected, and customer lawsuits or losses will be caused. Since being too “slow” has such an important effect, how do you optimize it?

    Optimization idea

    In the user-centric Performance Metrics article, there are four key Metrics of page rendering:

    With that in mind, let’s go back to the actual performance of previous projects:

    It can be seen that FP stays in the gray screen interface for a long time, the user is not clear whether the website is loading normally, the user experience is very poor.

    Consider this: if we could pre-render FCP or FMP full HTML documents to FP time, users would see the frame of the page and feel that the page is loading instead of cold gray screen, users would be more willing to wait for the page to load, thus reducing the attepoint rate. And this change is more obvious in the weak network environment.

    By comparing the DOM differences of FP, FCP and FMP, it is found that the differences are:

    • FP: Only one div root node.
    • FCP: Contains the basic framework of the page, but no data content.
    • FMP: Contains all the elements and data of the page.

    In the Vue life cycle, Mounted corresponds to FCP, and updated corresponds to FMP. So which lifecycle HTML structure should you use?

    mounted (FCP) updated (FMP)
    disadvantages
  • While the visual experience brings the FCP forward, the actual TTI time does not change much
  • Data needs to be obtained during the build, and the compilation speed is slow
  • There are differences between build and run time data
  • For pages with complex interactions, you still have to wait, and the actual TTI time doesn’t change much
  • advantages
  • Not affected by data, fast compilation speed
  • Good first screen experience
  • For purely presentable pages, THE FP and TTI times are nearly identical
  • Based on the above comparison, we finally choose to trigger built-time pre-rendering when Mounted. Since we adopt the CSR architecture and do not have Node as the middle layer, the pre-rendering of THE DOM content requires updating and replacing the original template during the project build and compilation.

    At this point, we have a rough idea of how to pre-render at build time.

    Pre-render scheme at build time

    Build-time pre-render process:

    Configuration is read

    Because spAs can consist of multiple routes, you need to determine which routes need to be pre-rendered based on the business scenario. Therefore, the configuration file here is mainly used to tell the compiler which routes need to be pre-rendered.

    In our architecture, the scaffolding is built on Webpack and can be customized to automate the build tasks and configurations.

    Trigger a build

    The project is primarily TypeScript, and with TS decorators, we have encapsulated the hook method for a uniform pre-render build, which triggers pre-render at build time in a single line of code.

    Decorator:

    Use:

    Build to compile

    From the flowchart, you need to start the simulated browser environment on the publisher and get the current page content through a pre-rendered event hook to generate the final HTML file.

    Since we were trying to pre-render early, there was no Headless Chrome, Puppeteer, Prerender SPA Plugin, etc., Phantomjs-prebuilt was used for selection (earlier versions of the Prerender SPA Plugin were also implemented based on PhantomjS-Prebuilt).

    The current HTML is available through the API provided by Phantom, as shown in the following example:

    In order to improve the construction efficiency, pre-render and build multiple configured pages or routes in parallel, and ensure that the construction can be completed within 5S. The flowchart is as follows:

    Scheme optimization

    Ideal is full, reality is very skinny. In actual production, the built-time pre-render solution encountered a problem.

    Let’s comb through the simplified project launch process:

    Develop -> compile -> online

    Pre-rendering failed to obtain files during build, resulting in a compilation error

    How to do?

    The request was hijacked

    Because we used to launch a simulated browser environment while doing the pre-render, the phantom provides an API that allows you to hijack the incoming request and hijack the request to get the CDN file to the local, which solves the problem at the root. The example code is as follows:

    Pre-render the development process and effects at build time

    Finally, the build-time pre-render development process is as follows:

    Development stage:

    • Introduces the method of pre-rendering build triggers in a single line through TypeScript decorators.
    • Modify the configuration file of the compile build before release.

    Release phase:

    • Start with a regular project build.
    • If there are pre-render related configurations, the pre-render build is triggered.
    • Get the final file through pre-render, and complete the release online action.

    The full user request path is as follows:

    With the use of build-time pre-rendering in the project, FCP time was reduced by 75%.

    Author’s brief introduction

    Han Yang, a senior R&D engineer of Meituan, has many years of experience in front-end research and development. He is responsible for the meituan Pay wallet team and the front-end basic technology of Meituan Pay.

    Recruitment information

    Our big front-end R&D team of Meituan financial service platform is growing rapidly. We welcome more excellent Web front-end R&D engineers to join us. Interested friends can send their resume to [email protected].