SSR best Practices
Turn-on seconds have a direct impact on retention, and statistics show that pages that take too long to load can lead to user churn. Around the group as a home appliance business company, for the H5 page second open rate has more stringent requirements, on the retail side of the main page (mobile channel, 3 c channel, activity pages) and other important traffic entrance we have adopted the SSR (server side rendering) technique to build a page, today he brought everyone understand once we grope out some of the best practices.
The past life of web pages
In the early Web applications, we actually use server-side rendering technology, such as JSP, ASP, PHP and other background templates to generate the page, the front end is to get the whole page, without their own DOM splicing. Later, with the development mode of front and back end separation, the two main rendering methods CSR and SSR were derived.
-
CSR: Client rendering, the entire rendering process is: Browser request URL –> server returns index.html(empty body, white screen) –> Request bundle.js again, route analysis –> The larger the browser renders bundle.js, the longer the white screen will be, the worse the user experience will be (of course, This can be optimized with the help of the package build tool.)
The interaction flow chart is as follows
-
SSR: server rendering, server rendering is divided into two steps:
-
Stage 1: Browser requests URL –> server route analysis, performs render –> server returns index.html(real-time rendered content, string) –> Browser render (this is a static page, not interactive, depends on server capabilities, that’s why it’s fast)
-
Stage 2: Browser requests bundle.js –> server returns bundle.js –> Browser route analysis, generate virtual DOM –> Compare DOM changes, bind events –> Secondary rendering (user interactive)
Let’s take a look at the entire interaction flowchart:
SSR construction logic
Understanding the similarities and differences between the two rendering modes, let’s take a look at the entire SSR construction logic (mainly vue-SSR as an example)
Take the picture on the official website as an example:
From the picture, we can know:
During the entire build process, we have two entrances, one is server-entry.js, which executes the server side logic, and the other is client.js, which executes the client side logic. The client bundle.node. js handles the server bundle for SSR, and the client bundle is returned to the user on request along with pages already rendered by SSR, and then executes “hydrate” in the browser to take over the following business logic from Vue.
Now that we understand the whole build logic, let’s look at how we use SSR to serve our projects.
Background of SSR construction project
The first screen is composed of multiple modules, and the second screen is the Feed flow of goods, which is easy for readers to understand. We abstract the page structure diagram:
(You are welcome to download and visit the app experience)
And these pages all have one thing in common:
- Display only, not strongly bound to the user’s state (no user login required)
- The page state is stable and the content does not change frequently
- Are the first entry points for important traffic (home page)
Due to the high requirements for the second opening rate and the load of the main traffic entrance, combined with the characteristics of the above page, we use SSR to improve user experience.
After a series of exploration and exploration, we finally use NuxT.js as our technology selection.
Here is why we use Nuxt.js as our technology selection. The main reasons are as follows:
- The c-side business of the group is mainly based on Vue technology stack, while the B-side technology stack is mainly based on React, so the React server rendering technology stack is not considered.
- Nuxt.js is an out-of-the-box server rendering framework, which does not require developers to build Vue+ VUE-server-renderer + VUex to integrate the server rendering framework, with relatively low access cost.
Best practices in SSR application
At present, the main capabilities of SSR are as follows:
- The first screen is rendered by the server and the second screen is rendered by the client. The module data of the first screen can be adjusted, which optimizes the performance and enriches the page configuration.
- Rationalize the use of cache to further improve user experience.
- Implement CSS injection to achieve the effect of skin on demand.
- Use ErrorBoundary to intercept errors so that component errors do not affect the entire white screen.
- Load the second screen of data as required, only when sliding to the visible range, load the second screen of data
- In view of the promotion scenario,docker expands its capacity in combination with server capabilities and various monitoring to ensure page stability.
Let’s explore the main ideas behind some of these abilities:
How to achieve server rendering for the first screen and client rendering for the second screen
This realization method is mainly combined with asyncData to get data asynchronously at the server side, and uses the feature of VUE dynamic component to adjust the rendering sequence of modules. Mounted Life hooks are only executed on the client side, using a feature that only renders components on the client side.
Sample code:
<template>
<! -- Server rendering, dynamically obtaining the first screen module and loading the corresponding module data, using error-boundary to intercept errors -->
<template v-for="(e, i) in structureOrder">
<error-boundary>
<component :info="activityState.structure[e]"
:is="Mutations.name2Component(e)"
class="anchor"
:id="e"
:key="i" :name="e" v-if="activityState.structure[e] || e === 'bar' "/>
</error-boundary>
</template>
<! -- Client render -->
<client-only>
<! -- Slide to visible range to load corresponding data -->
<div :is="listComponent" :tab="labelFilter"/>
</client-only>
</template>
Copy the code
Get data:
// The server renders data
async asyncData({app, route, req}) {
const initData = await app.$axios.$get(host, {
params: {
name: key, from, smark, keys: `structure,base,labelFilter,navigate,redPack,${elements}`
}, headers
})
const {structureInfo, structureOrder, restStructure, anchors} = Mutations.initStructure(initData)
return {
structureInfo,
restStructure,
StructureOrder, // dynamically returns the name of the corresponding module
useVideo: Mutations.checkUseVideo(req),
theme,
pageFrom: route.query.from,
isPOP,
anchors,
. formInfo
}
},
async mounted() {
// Get the client rendered data
const res = await this.initData()
},
Copy the code
How do I use ErrorBoundary to catch component-level errors and avoid the entire page going blank
As for ErrorBoundary, the component that captures errors, the main function of this component is to make the errors at the component level not spread to the page level and not cause the white screen of the whole page. Considering that the server rendering may have occasional errors and the state can easily become uncontrollable, it is necessary to use this capability. ErrorCaptured is a component that uses Vue to catch component-level errors. To see how the API is captured, see the documentation.
const errorBoundary = Vue => {
Vue.component('ErrorBoundary', {
data: () => ({ error: null }),
errorCaptured(err, vm, info) {
this.error = `${err.stack}\n\nfound in ${info} of component`
SentryCapture(err, 1) // Exceptions are reported to Sentry
return false
},
render() {
return (this.$slots.default || [null])[0] || null
}
})
}
// Register errorBoundary globally
Vue.use(errorBoundary)
Copy the code
How to implement CSS injection, implement page skin
The main function of this function is: it can customize the style of the activity page according to the configuration JSON file, so as to achieve “thousands and thousands of faces” (the key of a venue can be configured with one style, but the underlying code is one set), which makes the elements diversified and brings great improvement to the user experience visually.
Let’s take a look at the effect diagram first:
That’s it. With CSS injection, we can customize the style of the page based on different JSON files. It’s simple and efficient to maintain a single set of code.
The implementation logic is also very simple, mainly using the head method provided by the Nuxt.js framework:
head() {
/ / this. BaseInfo. AdditionStyle style from the json file
// Style overwrites can be achieved with CSS weights
return {
style: [
{cssText: this.baseInfo? .additionStyle ||' '.type: 'text/css'}
].
__dangerouslyDisableSanitizers: ['style'] // Prevent escaping special characters of some selectors
}
},
Copy the code
Not only that, but also to achieve JS injection, interested partners can go to understand, the underlying principle can understand the vuE-meta library
However, as the business continues to iterate, there are a number of points that can be optimized for this injection approach:
- Each activity page operation students have to maintain a JSON file, which contains lengthy CSS configuration fields, activity rules and so on, especially the CSS configuration fields, is a long CSS, to the operation and development of students bring great inconvenience;
- High maintenance cost, high learning cost, high operation cost;
- For UI students cost is also large, each time UI students need to design the activity page style;
At present, the group is using the Rubik’s Cube step by step to replace this method, the Rubik’s cube only needs to operate students drag drag, can generate an activity page, simple and efficient, students who want to know the Rubik’s Cube can continue to follow our public account
How can a component slide to visible range before loading data
In fact, this page optimization method is not only applicable to SSR, other non-SSR pages can also be used in this way to optimize;
Take a look at our implementation:
function asyncComponent({componentFactory, loading = 'div', loadingData = 'loading', errorComponent, rootMargin = '0px',retry= 2}) {
let resolveComponent;
return() = > ({
component: new Promise(resolve => resolveComponent = resolve),
loading: {
mounted() {
const observer = new IntersectionObserver(([entries]) => {
if(! entries.isIntersecting)return;
observer.unobserve(this.$el);
let p = Promise.reject();
for (let i = 0; i < retry; i++) {
p = p.catch(componentFactory);
}
p.then(resolveComponent).catch(e => console.error(e));
}, {
root: null,
rootMargin,
threshold: [0]
});
observer.observe(this.$el);
},
render(h) {
return h(loading, loadingData);
},
},
error: errorComponent,
delay: 200
});
}
export default {
install: (Vue, option) => {
Vue.prototype.$loadComponent = componentFactory => {
return asyncComponent(Object.assign(option, {
componentFactory
}))
}
}
}
Copy the code
The implementation principle is mainly to use VUE high-order components, elements reach the visible range, delay loading components;
Check out the renderings:
As you can see, the corresponding interface is requested only when the bottom item Feed is visible
How to ensure page stability for large promotion scenarios
The so-called big promotion scenario refers to scenarios like 6.18 and Double 11. In this scenario, in the face of large traffic, how to ensure page stability? SSR is cpu-intensive task, which means it consumes server resources. The group mainly adopts the following strategies at present:
- The interface is pressed to simulate page performance and interface response speed in high concurrency scenarios.
- The group has realized a monitoring system, which can monitor the consumption of CPU and memory in real time.
- A server capacity expansion solution is required. For example, docker can be connected to achieve real-time server capacity expansion
How to leverage caching
Please move to the group a front-end bighead to write the public number article: Nuxt to achieve SSR page performance optimization further exploration and practice
Finally, look at our final implementation:
As you can see, the first screen rendering time is 594ms and the opening rate is around 87%.
The shortage of the SSR
The use process of SSR is not smooth. In the process of use, several shortcomings are summarized:
- Developers are required to learn additional knowledge, such as Linux and Node, which requires a certain back-end thinking.
- Server rendering interface is not convenient to capture packets, we can not capture server interface request in the client, but for students using Mac computer development, you can use Proxychains -ng to capture server interface request
- Lengthy configuration environment process, each development tuning needs to configure the back-end host
- It has requirements on server resources. The more concurrency, the more resources are consumed
- Server rendering can occasionally go wrong and require a degradation plan
As for how to choose, depending on the project needs of each student, as well as the application of the scene;
conclusion
The use of SSR has advantages and disadvantages. We should combine our own business characteristics to develop appropriate solutions. Its advantages are fast and conducive to SEO, but its disadvantages are also obvious. The group also has its own set of solutions to optimize client rendering, making the user experience as close as possible to SSR. Only when the application of each kind of technology is practiced can the advantages and disadvantages be known and the collision can be generated. This article is just a simple to take you to understand the use of SSR on a business line, the aspects described are just the tip of the iceberg, hope to give the majority of developers to bring a certain inspiration, predecessors plant trees, descendants enjoy the shade, thanks to turn around FE predecessors left precious wealth.
The last
The resources
Nuxt website: https://www.baidu.com/link?url=xy0d8KPUgTmiVoGge6g-FgdeqjJSTjxdpT0tpxZzBG_&wd=&eqid=db50cacf00052e5e000000066081587d
Vue SSR guide: https://ssr.vuejs.org/zh/guide/