Web rendering
Six render modes you need to know about
Let’s start with web browsing
Here are some terms related to web performance that will be referenced in the future:
TTFB``: Time to First Byte
– Is considered the time between clicking the link and the first part of the content.
FP``: First Paint
– The time when any pixel is first visible to the user.
FCP``: First Contentful Paint
– The time when the requested content (the body of the article, etc.) becomes visible.
TTI``: Time To Interactive
– The time when the page becomes interactive (connected events, etc.).
SSR
First, SSR: Server-side Rendering.
SSR has two modes, single page and the single page mode, the first is a back-end rendering single-page applications, for the first time was at the time of loading for the first time, after the end of the current path component of the page rendering and data request, assembled into HTML returned to the front end, users can quickly see, see page when HTML in JS resources after completion of loading, What is left to execute and run is a generic single-page application.
The second is a back-end template rendering mode that uses back-end routing entirely. They differ in the degree to which back-end routing is used.
SSR has two obvious advantages: fast first load and SEO.
Why is the first load fast? A normal single-page application will need to load all the related static resources for the first time, and then the core JS will start executing. This process will take some time, and then the network interface will be requested, and finally the complete rendering will be completed.
SSR mode, the back-end intercepted routing, find the corresponding components, prepare rendering component, all JS resources locally, ruled out JS resources network load time, then only need to the current routing component rendering, and the ajax request page, may be on the same server, if so speed will be a lot faster. Finally, the back end passes the rendered page back to the front end.
Note: the page can be displayed quickly, but because the current return is just a simple display of DOM, CSS, JS related events and so on in the client are not bound, so it is necessary to render the current page again after LOADING JS, which is called isomorphism. So SSR is faster to show the content of the page first, so that users can see it first.
Why is SEO friendly? Because when the search engine crawler crawls the page information, it will send HTTP request to obtain the web content, and the data rendered by our server for the first time is returned by the back end, which has already rendered the title, content and other information, so that the crawler can easily grab the content.
SSR may take a little time to prepare content (long TTFB), but there are fast FCP as well as TTI.
CSR
As front-end functionality and interaction become more complex, we need client-side Rendering and SPA architectures to build interactive web pages: Angular, React, Vue, etc.
The Server only sends back HTML without content, and then renders the page according to different URLS after the JS loading is completed. The subsequent pages are in the front end, and render the page with JS.
CSR and SPA greatly improve the experience of front-end development and the interactivity of pages, but problems arise:
- Bigger and bigger
Javascript Bundle Size
- The page is blank at the beginning and needs to wait
JS
Load execution only has content, which can be detrimentalSEO
Heavy Javascript Code causes slow loading and execution.
FCP, TTI longer, means that the user has a long window of blank or incomplete pages that are not yet interactive.
About the SSR
pureSSR
Traditional techniques for presenting static content still require a SPA interactive experience.
The best solution is the combination of SSR+SPA, that is, in the implementation of server-side rendering, but also to achieve client rendering, the first access to the page is server-side rendering, based on the first access to the subsequent interaction is SPA effect, so as to retain the advantages of the two technologies.
The two technologies have a lot of reusable code, client-side routing, server-side routing, client-side Redux, server-side Redux, etc. The maximum reuse of this code is isomorphism.
Now the server side rendering is basically SSR+SPA isomorphic rendering, not traditional server side rendering.
Here’s a quick look at what an SSR-core-React framework would look like:
import { render } from 'ssr-core-react'
@Get('/')
@Get('/detail/:id')
async handler (): Promise<void> {
try {
this.ctx.apiService = this.apiService
this.ctx.apiDeatilservice = this.apiDeatilservice
const stream = await render<Readable>(this.ctx, {
stream: true
})
this.ctx.body = stream
} catch (error) {
console.log(error)
this.ctx.body = error
}
}
Copy the code
When visit http://localhost:3000 or http://localhost:3000/detail/xxx, request first after registered in the Controller of the routing. And the corresponding function for processing.
The processing logic of the sample function calls the Render method provided by the SSR-core-xxx module to render the front-end component corresponding to the current request. The result is a complete HTML document structure with HTML and meta tags. The returned document structure already contains code for loading client resources with the Script tag.
Render method:
The data request will be invoked during server rendering execution. During client activation, data retrieved by the server and injected into the window is reused for initialization. It will not be retrieved on the client side. When the client performs a front-end route switch, it calls the fetch corresponding to the page it is going to.
Implement ideas
The core implementation is divided into the following steps:
- The back end intercepts the route and finds what needs to be rendered based on the path
react
Page componentsX
- The calling component
X
Initialization needs to request the interface, synchronization after obtaining data, usereact
therenderToString
Method to render a component to render a node string. - Back-end get the basics
HTML
Insert the rendered node string into thebody
Can also be operated ontitle
.script
Such as node. Return fullHTML
To the client. - The client gets the value returned by the backend
HTML
To display and loadJS
Finally finishreact
Isomorphism.
-
Registered routing
import Index from ".. /pages/index"; import List from ".. /pages/list"; const routers = [ { exact: true, path: "/", component: Index }, { exact: true, path: "/list", component: List } ];Copy the code
Configure the mapping between routing paths and components so that they can be used by both client and server routes.
import Index from ".. /pages/index"; import List from ".. /pages/list"; const routers = [ { exact: true, path: "/", component: Index }, { exact: true, path: "/list", component: List } ]; Export const clientPages = (() => {const pages = {}; routers.forEach(route => { pages[route.path] = route.component; }); return pages; }) (); export default routers;Copy the code
Server-side processing
import { clientPages } from "./.. /.. /client/router/pages"; router.get("*", (ctx, next) => { let component = clientPages[ctx.path]; if (component) { const data = await component.getInitialProps(); Create const dom = renderToString(React. CreateElement (Component, {ssrData: data})}})Copy the code
After the component is matched, the component’s getInitialProps method is executed. This method is a encapsulated static method that gets the Ajax data needed for initialization. It gets it synchronously on the server side and then passes in the component props parameter ssrData to perform the component rendering. This method is still an asynchronous request on the client side.
-
Client-side rendering
import React from "react"; Export default Class Base extends React.Component {// Override Obtains asynchronous data that requires the server to render for the first time. Static Async getInitialProps() {return null; } static title = "react ssr"; // Do not overwrite the page component constructor(props) {super(props); // If static state is defined, state should take precedence over ssrData by lifecycle if (this.constructive.state) {this.state = {... this.constructor.state }; } // if (props. SsrData) {if (this.state) {this.state = {... this.state, ... props.ssrData }; } else { this.state = { ... props.ssrData }; }}} Async componentWillMount() {// Client runtime if (typeof Window! = "undefined") { if (! This. Props. SsrData) {/ / static method, through the constructor for const data = await this. Constructor. GetInitialProps (); if (data) { this.setState({ ... data }); Document.title = this.constructive.title; }}}Copy the code
If in a client environment, there are two cases.
The first: When the user enters the page for the first time, it is the server side that requests the data. After obtaining the data, the server side renders the components at the server side. At the same time, the data will be stored in the SCRIPT code of HTML to define a global variable ssrData. React registers a single page application and passes global ssrData to the page component. In this case, the page component can continue to use the data from the server during the client’s isomorphic rendering. In this way, the consistency of isomorphism is maintained and repeated requests are avoided.
In the second case, if the current user switches routes in a single page and there is no server rendering, the getInitialProps method is executed, returning the data directly to state, which is almost the same as executing the request in Willmount.
Here comes the most important question! —- How do you distinguish between the first and later renderings? When to pass ssrData the logic is as follows:
import { BrowserRouter, Route, Switch, withRouter } from "react-router-dom"; import React from "react"; import routers from "./pages"; Const router = ({ssrData, ssrPath}) => {// Insert SSR data into all pages. After the first router is received, other pages need to discard the data. Call getInitialProps to initialize the phmore. forEach(item => {let _ssrData = null; If (ssrPath == item.path) {_ssrData = ssrData; if (ssrPath == item.path) {_ssrData = ssrData; } item.render = () => { item.component = withRouter(item.component); Return < item.ponent ssrData={_ssrData} />; }; }); return ( <BrowserRouter> <Switch> {routers.map((route, i) => ( <Route key={i} exact={route.exact} path={route.path} render={route.render} /> ))} </Switch> </BrowserRouter> ); }; export default router;Copy the code
-
Dehydrate
Prepare index. HTML
<! DOCTYPE html> <html lang="en"> <head> <title>/*title*/</title> </head> <body> <div id="root">$$$$</div> <script> /*getInitialProps*/ </script> <script src="/*app*/"></script> <script src="/*vendor*/"></script> </body> </html>Copy the code
Replace the node
indexHtml = indexHtml.replace("/*title*/", component.title); indexHtml = indexHtml.replace( "$$$$", renderToString( React.createElement(component, { ssrData: data }) ) ); indexHtml = indexHtml.replace( "/*getInitialProps*/", `window.ssrData=${JSON.stringify(data)}; window.ssrPath='${ctx.path}'` ); indexHtml = indexHtml.replace("/*app*/", bundles.app); indexHtml = indexHtml.replace("/*vendor*/", bundles.vendor); ctx.response.body = indexHtml; next();Copy the code
The component is serialized into a static HTML fragment that you can still see, but you can no longer interact with.
-
Water hydrate
When the client JS is loaded, react is run and the isometric method reactdom.hydrate is executed instead of the usual reactdom.render.
The React – DOM provides a hydrate method similar to the Render method for secondary rendering.
It will reuse the original existing DOM nodes during rendering, reducing the cost of regenerating nodes and deleting the original DOM nodes, only for event processing binding.
The difference between Hydrate and Render is that hydrate re-uses existing nodes and Render re-renders all of them.
So Hydrate is mainly used for secondary rendering of nodes rendered by servers to improve the first loading experience.
import React from "react"; import { hydrate } from "react-dom"; import Router from "./router"; class App extends React.Component { render() { return <Router ssrData={this.props.ssrData} ssrPath={this.props.ssrPath} / >; }} hydrate( <App ssrData={window.ssrData} ssrPath={window.ssrPath} />, document.getElementById("root") );Copy the code