15
头图

Rendering on the Web

As developers, we often face decisions that affect the entire architecture of an application. One of the core decisions that Web developers must make is where to implement logic and presentation in their applications. This can be difficult because there are many different ways to build a website.

Our understanding of this field stems from our work with large websites in Chrome over the past few years. Broadly speaking, we encourage developers to consider server rendering or static rendering, rather than completely rehydration.

To better understand the architecture we chose when making this decision, we need to have a deep understanding of each method and the consistent terminology used when talking about them. The differences between these methods help illustrate the trade-offs of rendering on the web from a performance perspective.

Rendering

  • SSR: Server-Side Rendering-Render the client or general application as HTML on the server.
  • CSR: Client-side rendering-rendering the application in the browser, usually using the DOM.
  • Rehydration: "Enable" JavaScript views on the client side so that they can reuse the DOM tree and data of the HTML rendered by the server.
  • Prerendering: Run the client application at build time to capture its initial state as static HTML.
  • TTFB: Time to First Byte-is considered the time between clicking the link and entering the first content.
  • FP: First Paint-any pixel is visible to the user for the first time.
  • FCP: First Contentful Paint-The time when the requested content (article body, etc.) becomes visible.
  • TTI: Time To Interactive-The time when the page becomes interactive (event connection, etc.).

Server Rendering

Server rendering generates complete HTML for pages on the server in response to navigation. This avoids the extra round trip of data fetching and templating on the client, because it is processed before the browser gets the response.

Server rendering usually produces fast first draw (FP) and first content draw (FCP). Running page logic and rendering on the server can avoid sending a lot of JavaScript to the client, which helps to achieve fast interaction time (TTI). This makes sense, because with server rendering, you are actually just sending text and links to the user's browser. This method can be well adapted to various devices and network conditions, and opens up interesting browser optimizations, such as streaming document parsing.

With server rendering, users are unlikely to wait for CPU-bound JavaScript processing before using your website. Even if third-party JS cannot be avoided, using server rendering to reduce your own first-party JS cost can also provide you with more "budget." However, this method has a major disadvantage: it takes time to generate the page on the server, which usually results in a slower time to first byte (TTFB).

Whether server rendering is sufficient for your application depends largely on the type of experience you are building. There is a long-standing debate about the correct application of server rendering vs. client rendering, but it is important to remember that you can choose to use server rendering for some pages over others. Some websites have successfully adopted hybrid rendering technology. The Netflix server renders its relatively static landing page, while prefetching JS for interactive-intensive pages, providing these heavier client-side rendering pages with better opportunities for fast loading.

Many modern frameworks, libraries, and architectures make it possible to present the same application on the client and server. These techniques can be used for server rendering, but it is important to note that the architecture where the rendering occurs on the server and the client is their own solution class, with very different performance characteristics and trade-offs. React users can use renderToString() or solutions built on it, such as Next.js for server rendering. Vue users can view Vue's server rendering guide or Nuxt. Angular has a Universal framework. Most popular solutions use some form of hydration, so please pay attention to the method used before choosing a tool.

Static Rendering

Static rendering occurs at build time and provides fast first draw, first content draw, and interaction time-assuming a limited amount of client-side JS. Unlike server rendering, it also manages to achieve consistently fast first byte times because the HTML of the page does not have to be generated on the fly. Generally, static rendering means generating a separate HTML file for each URL in advance. By generating HTML responses in advance, static renderers can be deployed to multiple CDNs to take advantage of edge caching.

Solutions for static rendering come in various shapes and sizes. Tools like Gatsby are designed to make developers feel that their applications are rendered dynamically, rather than being generated as a build step. Other companies like Jekyll and Metalsmith embrace their static nature and provide a more template-driven approach.

One of the disadvantages of static rendering is that a separate HTML file must be generated for each possible URL. If you cannot predict the content of these URLs in advance, or for sites with a large number of unique pages, this may be challenging or even impossible.

React users may be familiar with Gatsby, Next.js static export, or Navi-all of which can be easily authored with components. However, it is important to understand the difference between static rendering and pre-rendering: statically rendered pages are interactive and do not need to execute a lot of client-side JS, while pre-rendering improves the first drawing or first content drawing of a single-page application that must be launched by the client End to make the page truly interactive.

If you are not sure whether the given solution is static rendering or pre-rendering, please try the following test: disable JavaScript and load the created web page. For statically rendered pages, most features still exist without JavaScript enabled. For pre-rendered pages, there may still be some basic functions, such as links, but most pages will be lazy.

Another useful test is to use Chrome DevTools to reduce network speed and observe how much JavaScript has been downloaded before the page becomes interactive. Pre-rendering usually requires more JavaScript to gain interactivity, and JavaScript tends to be more complex than the progressive enhancement methods used for static rendering.

Server Rendering vs Static Rendering

Server rendering is not a panacea-its dynamic nature will bring a lot of computational overhead costs. Many server rendering solutions do not refresh in advance and may delay TTFB or double the data sent (for example, inline state used by JS on the client). In React, renderToString() can be slow because it is synchronous and single-threaded. Making server rendering "correct" may involve finding or building solutions for component caches, managing memory consumption, applying memory technology, and many other issues. You usually process/rebuild the same application multiple times-once on the client and once on the server. Just because server rendering can make something show up faster doesn't suddenly mean you have to do less work.

SSR generates HTML on demand for each URL, but may be slower than just serving statically rendered content. If you can do extra work, server rendering + HTML caching can greatly reduce server rendering time. Compared with static rendering, server rendering has the advantage of being able to extract more "real-time" data and respond to a more complete set of requests. Pages that need to be personalized are specific examples of request types that don't work well with static rendering.

When building a PWA, server rendering can also provide interesting decisions. Is it better to use full-page service worker caching or just the server to render a single content?

Client-Side Rendering (CSR)

Client-side rendering (CSR) refers to the use of JavaScript to render a page directly in the browser. All logic, data acquisition, templates, and routing are processed on the client instead of the server.

For mobile devices, client-side rendering may be difficult to obtain and keep it fast. If you do the least amount of work, keep a tight JavaScript budget and deliver value with as little RTT as possible, it can approach the performance of pure server rendering. Use HTTP/2 server push or \<link rel=preload> to deliver key scripts and data faster, which allows the parser to work for you faster. Models like PRPL are worth evaluating to ensure that the initial and subsequent navigation feels instantaneous.

The main disadvantage of client-side rendering is that the amount of JavaScript required will grow as the application grows. This becomes especially difficult with the addition of new JavaScript libraries, polyfills, and third-party code, which compete for processing power and usually must be processed before the page content is rendered. Experiences built with CSRs that rely on large JavaScript packages should consider active code splitting and ensure that JavaScript is lazily loaded-"only provide the services you need when you need them". For experiences with little or no interactivity, server rendering can represent a more scalable solution to these problems.

For people who build single-page applications, recognizing the core part of the user interface that most pages share means that you can apply application shell caching technology. Combined with service workers, this can significantly improve the perceived performance during repeated visits.

Combining server rendering and CSR via rehydration

Often referred to as Universal rendering or simply "SSR", this method attempts to balance the trade-off between client-side rendering and server-side rendering at the same time. Navigation requests such as full page loading or reloading are processed by the server that renders the application as HTML, and then the JavaScript and data for rendering are embedded in the generated document. If implemented carefully, this will achieve a fast first rendering of the content, just like server rendering, and then "pick up" by rendering again on the client side using a technique called (re)Hydration. This is a novel solution, but it may have some considerable performance flaws.

The main disadvantage of SSR with rehydration is that it has a significant negative impact on interaction time, even if it improves First Paint. SSR pages usually look like they are loading and interacting, but they cannot actually respond to input until the client-side JS is executed and event handlers are attached. On mobile devices this may take a few seconds or even minutes.

Perhaps you have experienced this situation yourself-clicking or clicking did nothing for a while after it appeared that the page had loaded. This quickly becomes frustrating... "Why is nothing happening? Why can't I scroll?"

A Rehydration Problem: One App for the Price of Two

The rehydration problem is usually worse than the delayed interaction caused by JS. In order to allow client-side JavaScript to accurately "receive" the location of the server interruption without having to re-request the server for all the data used to render its HTML, current SSR solutions usually write the response serialized data dependency from the UI as a script tag Into the document. The generated HTML document contains a high degree of repetition:

As you can see, the server responds to the navigation request and returns the description of the application UI, but it also returns the source data used to compose the UI, as well as a complete copy of the UI implementation, and then starts on the client. Only after bundle.js finishes loading and After execution, this UI will become interactive.

Performance metrics collected from real websites that use SSR rehydration indicate that it should be strongly recommended not to use it. In the final analysis, the reason comes down to the user experience: in the end, it is easy for users to fall into the "horror valley".

Streaming server rendering and Progressive Rehydration

In the past few years, server rendering has seen many developments.

Streaming server rendering allows you to send HTML in chunks, which the browser can gradually render as they are received. This can provide fast first drawing and first content drawing because the mark reaches the user's hands faster. In React, the stream is asynchronous in renderToNodeStream()—compared to synchronous renderToString—meaning that backpressure is handled well.

Progressive rehydration is also worth paying attention to, which is something React has been exploring. Using this method, the various parts of the application rendered by the server will "start" over time, rather than the current common method of initializing the entire application at once. This helps to reduce the amount of JavaScript required to make the page interactive, because it can postpone client upgrades on low-priority parts of the page to prevent blocking the main thread. It can also help avoid one of the most common SSR Rehydration pitfalls, in which the DOM tree rendered by the server is destroyed and then rebuilt immediately-usually because the initial synchronous client rendering requires unprepared data and may be waiting for Promise resolution.

SEO Considerations

When choosing a strategy to present on the Web, the team usually considers the impact of SEO. Server rendering is usually chosen to provide a "full look" experience that crawlers can easily interpret. Crawlers may understand JavaScript, but there are usually some notable limitations in the way they are presented. Client-side rendering can work, but usually requires additional testing and work. If your architecture is largely driven by client-side JavaScript, then dynamic rendering has recently become an option worth considering.

If in doubt, the Mobile Friendly test tool is very useful for testing whether the method you choose meets your expectations. It shows how any page is displayed in the Google crawler, the serialized HTML content found (after JavaScript is executed), and a visual preview of any errors encountered during rendering.

Summarize

When deciding on a rendering method, measure and understand what your bottlenecks are. Consider whether static rendering or server rendering can allow you to complete 90% of the work. It is perfectly possible to use the least JS to publish HTML mainly for interactive experience. This is a handy infographic showing the server-client scope:

More original articles by Jerry, all in: "Wang Zixi":


注销
1k 声望1.6k 粉丝

invalid