Key points in choosing SSR, CSR and streaming rendering
How many times will the same data be retrieved, at which level it will be covered, and who will stop it after an error occurs. Determine whether the front-end solution will be written messily earlier than "how much faster is the first screen?"
Many teams discuss SSR, CSR, and streaming rendering, and the first thing they ask about is performance.
How long is the first screen time, can LCP be suppressed, can SEO help, can interfaces be parallelized, and can Streaming be skeletonized earlier. Of course these are important, but when it comes to projects, the first thing that messes up the page is often how many times the same data has been fetched, at which layers it is cached, and who will close it after failure.
My judgment is: **The key to the front-end rendering strategy is not who is more advanced, SSR, CSR, or streaming rendering, but that the same page data has to go through several times of fetching, several times of hydration, and several sets of failure rollbacks. Once a page pursues SEO, interaction speed, and cache hits at the same time, the first things to get out of control are usually data consistency, error coverage, and troubleshooting costs. **
What really messes up the page is usually not the rendering method itself.
I have seen many pages, and at first it was just a very ordinary content details page:
- Click on the list page for details;
- We hope search engines can crawl the first screen, so we need SSR;
- There are interactions such as collections, comments, and recommendations on the page, so the client has to continue to make requests;
- In order to make the first screen faster, the interface is split into main data and secondary blocks;
- In order to improve the hit rate, the server adds edge caching, and the client adds a layer of request caching.
Up to this point, every decision can be justified.
The problem is that after these decisions are stacked together, there are actually at least four paths for “data to reach the scene” on the page:
- The data obtained during the first rendering of the server;
- The data brought down when the browser is hydrated;
- After the client is mounted, it resends the request to obtain the new data;
- Derived data that is locally updated after user interaction.
If there is no clear priority between these four paths, the page will start to have some familiar strange problems:
- What you see in HTML is the old price, and it will flash into the new price after hydration;
- The first screen rendering shows “Favorite”, but after the client takes over, it changes back to “Not Favorite”;
- The recommended block is one step late, and the updated page is partially pushed back to the old value;
- When the server reported an error, it showed one set of details, and after the client retried, it displayed another set of details.
These problems are caused by the fact that the same page status is generated and overwritten multiple times, but no one defines who has the final say.
SSR solves the first screen visibility, but does not automatically solve the source of data truth.
A common situation is to think of SSR as “spit out the correct page first”, but this sentence is only true under a very narrow premise: **The data obtained by the server is the final data that the user should really see this time. **
In reality, this premise often does not hold true.
For example, the product information on the home screen of the details page is rendered by the server, but the following information is often not:
- Whether the user has clicked on favorites;
- Which set of recommendations is currently hit by the experimental bucket;
- Geographically relevant inventory;
- Login-related prices or rights;
- Asynchronous updates completed just after the page is mounted.
At this time, SSR can only guarantee that “first spit out one version to display the results”, but cannot guarantee that this version of the results will be the final truth of the entire page.
Once the team also requires that “the server produces HTML first, and the client takes over and then adds personalization”, the system will naturally split into two sets of judgments:
- The server determines what the page should look like first;
- The client determines what the page should eventually look like.
If the access conditions, caching timing, and fault tolerance strategies on both sides are not completely consistent, the page will definitely be inconsistent.
So what SSR should really ask is:
- How long can the data output by the server be kept fresh this time;
- Which fields are allowed to be covered after hydration;
- Which fields must be based on the latest results from the client;
- Are server-side failures and client-side failures the same set of semantics?
Without clarifying these issues first, SSR will only send a certain version of the answer to the user at an earlier point in time, rather than solving consistency at a higher level.
The real problem with CSR is that it is easy to write the refresh semantics loosely
CSR is often criticized for being slow above the fold, and rightly so. But my more common problem is that CSR projects tend to write “re-pull the data again” as an unconstrained default action.
The page is initialized and pulled once; Cut the tab and pull it once; Pull the window focus again; After the user’s operation is successful, pull it again easily; The request library automatically retries again.
The code looks natural, but the final effect may be:
- The page status is overwritten multiple times in a short period of time;
- The old request returned later will override the new request returned first;
- Optimistic update just took effect and was pushed back by full refetch;
- A certain local component is refreshed separately, and the page-level status is reassembled into another version of the result.
This kind of problem is particularly common in CSR, because the client naturally regards “pulling the latest data again now” as a low-cost action. But as long as a page has both a list, a summary, and interactive feedback, refetch is an act of re-judging the status of the entire page.
If there are no clear rules, CSR will eventually become: it doesn’t matter who gets the data first, who has the final say who gets the data successfully in the end.
Of course, the page is unstable at this time. It has less to do with the CSR itself and more to do with opening the write path too wide.
The most easily underestimated cost of streaming rendering is the cost of state interpretation caused by “batch arrival”
Streaming rendering in the past two years can easily be said to be a net benefit:
- The skeleton arrives earlier;
- Key content comes out first;
- It doesn’t matter if the secondary block is later;
- User experience will be smoother.
These are all true. But it has a price that is rarely seriously considered in plan reviews: the ** page no longer only has two states: “arrived” or “not arrived”, but instead has multiple blocks arriving in batches, errors in batches, and rollbacks in batches. **
Once the page is split into asynchronous fragments such as main content, comments, recommendations, advertising spaces, and personalized floating layers, it is not just performance that needs to be dealt with, but the following engineering issues:
- Whether the master data version that a certain late-arriving block relies on and the first screen version are the same;
- When the main block succeeds and the secondary block fails, whether the entire page can still be considered successful;
- Whether the content already output in the stream can be overturned by subsequent fragments;
- The user has already started the operation after the first page fragment arrives. Will the subsequent fragments overwrite the interaction results.
This cost is not abstract. I have seen a page that SSRs out the main information and adds related recommendations and user rights in a streaming format. It turns out that the most difficult bug to troubleshoot online is “the rights copy seen by some users will switch twice within two seconds.”
Finally checked, it is:
- The first segment of the server uses the old equity snapshot in the public cache;
- Subsequent streaming segments use a new interface with user mode;
- After the client is hydrated, the display fields are recalculated based on the local login status.
All three sets of sources make sense, but when put together, the page doesn’t feel like a system.
What should be designed first is the main data line of the page.
At the end of the discussion on rendering strategies, I became more and more concerned about whether the page had a clear main line of data.
The so-called data mainline is to determine the following things in advance:
1. Which data is the first screen baseline?
When the first screen HTML comes out, which fields can already be regarded as displayable true values and which are just placeholder results must be clearly stated.
For example:
- The text of the article can be based on SSR results;
- The user’s like status can only be determined by the client’s login status request;
- The recommended block is declared from the beginning as “asynchronous completion, does not participate in the first screen true value”.
As long as this layer is not defined, each subsequent round of requests will compete with the previous round for interpretation rights.
2. Which fields are allowed to be overwritten, and which fields can only be filled in incrementally?
Many pages are written cluttered because all data that comes later is defaulted to “the latest and more trustworthy”. Not really.
Some fields are suitable for coverage, such as inventory, price, and number of people online; Some fields can only be filled in, such as comment pagination and recommendation list; Some fields must be overwritten with version judgment, such as the collection status that the user has just operated.
If there are no field-level override rules, the more subsequent requests there are, the greater the probability that the old value will replace the new value.
3. Are server-side failure and client-side failure the same semantics?
This is easily overlooked.
In some systems, when the server request fails, it will be directly downgraded to the default module; when the client fails, an independent error retry button is displayed. The result the user sees is:
- When refreshing the page, this content disappears quietly;
- After the page is mounted, this content suddenly pops up an error state.
This is because the system does not have a unified explanation for “failure”.
In the same block, if the failure semantics of the server and client are different, it will be difficult to answer when troubleshooting: Is this a normal downgrade or an abnormal display?
A common misunderstanding: taking “pulling the latest data once more” as insurance
When many teams encounter consistency problems, their first reaction is “then the client will pull the latest data again.”
This trick is often effective in the short term because it does refresh some of the old SSR data. But in the long run, it can easily escalate the problem from “the first screen is occasionally a bit old” to “the entire page is competing for final interpretation rights”.
The most typical failed link is this:
- The server first takes version A data and renders the first screen;
- After the browser is mounted, the client automatically pulls version B;
- The user immediately clicked on the collection, and the local optimistic update changed to C;
- Version B requests a late return and pushes C back;
- Return to the collection interface and the page changes back to D.
Technically, every step is “right”.
But to users, the page changed answers four times in three seconds. At this point, it is difficult to say whether the core of the problem is SSR or CSR, because the real problem is: **The page does not specify which priority should be given to interactive writing or full refresh. **
Counterexample: Not all pages are worthy of SSR or streaming
There is also a very real misunderstanding, which is to regard the rendering strategy as a list of platform capabilities.
If the framework supports SSR, it tends to be site-wide SSR; Support streaming rendering and start removing blocks; If you are worried about SEO, use the server side for all detail pages by default; If you are worried about the slowness, add another layer of client cache.
Finally, the system’s interpretation costs skyrocketed.
Some pages are simply not worth complex rendering strategies:
- Backend pages with strong login status, strong personalization, and low search engine value;
- A transaction page whose real-time data is much higher than the value of a static first screen;
- An operation page with little content on the first screen but a lot of interactions.
If SSR is installed on such pages, and then client-side supplementary pulling and partial streaming are added, the benefits are often not as good as writing the data under CSR honestly.
On the contrary, content-based detail pages, public landing pages, and information pages with a stable structure are more suitable for making the most of SSR or streaming revenue. The premise is still whether the data boundaries of the page can be clearly separated**.
To choose a method, look at three things first
If you really want to make a judgment between SSR, CSR, and streaming rendering, I recommend reading these three things first instead of reading the framework promotional page first.
First, check whether the content on the first screen has a stable true value.
If most of the content on the first screen can be obtained on the server side, and the difference in user mode is small, SSR will be more stable.
If the first screen relies heavily on the login state, device state, and real-time state, and the server only gets an approximate value that expires quickly, then the SSR revenue will be discounted because the client will soon have to recalculate it again.
Second, check whether the page allows batch explanation
If the page can naturally be reached in blocks, such as text first, comments last, and recommendations last, streaming rendering is more valuable.
If several blocks in the page share a large amount of state, and the late arrival of one will overturn the previous two, then streaming rendering will not only bring performance optimization, but also state coordination costs.
Third, see if interactive writing will conflict with the refresh path.
As long as there are actions such as collections, shopping carts, forms, and permission switching on the page that will change the status immediately, it is necessary to focus on whether the interactive writing path and the page refresh path will cover each other.
This matter is not fixed, no matter which rendering strategy you choose, it will just make mistakes in another way.
Applicable boundaries
This article mainly discusses:- Simultaneously pursue SEO, above-the-fold speed and personalized web pages;
- The same page has both server-side first rendering, client-side supplementary rendering, and local asynchronous blocks;
- Hydration, caching, and streaming assembly issues that are naturally encountered when using modern front-end frameworks.
If the page is very simple, either pure content display or pure background operation, the complexity of the rendering strategy will not be so high. The problem most likely to break out is the page that “looks like just a details page, but actually carries content distribution, business conversion, and user interaction at the same time.”
Summary
SSR, CSR, and streaming rendering are not wrong. What is wrong is to regard them as pure performance switches.
Once a page has first-screen requirements, caching requirements, and interaction requirements at the same time, what really gets out of control first is which version of the data is counted, who will be responsible for failure, and who takes priority in the interactive writing and refreshing paths.
Therefore, what the front-end rendering strategy should really determine first is how many times will this page of data be generated, how many times will it be overwritten, how many times will it fail to fall back, and which layer will finally close it.
This matter is not clear. The more advanced the rendering scheme, the more it seems that the page is written by many sets of reasonable mechanisms at the same time.
读完之后,下一步看什么
如果还想继续了解,可以从下面几个方向接着读。