What is a reducible isomorphic framework

Here refers to the degrade and server degrade similar meaning (master logic failure to adopt the standby logic process), considering the server side rendering concurrency and high load, when the master server can not provide normal service can be degraded to “low power mode”, the use of client rendering to reduce server load.

Selection of the framework

next.js

I have a look at next. Js, their development method is very novel, especially the routing configuration, do not need to configure their own, just do it according to his rules. But I this person is more stable, this convention is greater than the configuration of the way for me to always feel there will be pits, unable to control their own, can not find a solution to see the source code to know.

Or stability of I, after all, rendering of a service is a need to consume the server performance, especially high concurrency and out of memory, may cause the server response slow even hang up, so I hope I can also downgraded, the framework of the relegation is similar to downgrade, high load, the server switch another machine, Start “low power mode “(i.e. client-rendered single page application) to continue running, I checked next. Js doesn’t seem to have this feature (please let me know in the comments if you do).

cra-ssr

Really can not find to render the service side and the client rendering framework, finally I can find the project https://github.com/cereallarceny/cra-ssr, transformation is very successful, process encountered many pits, here share some process steps.

Transformation journey

In fact, CRA – SSR project itself has done a good job of server isomorphic rendering, before the transformation of the best to understand the process principle of CRA – SSR isomorphic rendering and then look at the following steps, the following is the flow chart of CRA – SSR isomorphic rendering

The following is the flow chart of the degraded transformation:

The frontload in yellow in the figure above is used to prefetch data from the interface pre-processed during server rendering, and then put into the prepared global_state. Global_state is a store that stores the prefetch data and connects it to the component rendering. It can also be used as a global variable for both server and client.

The blue node is the server environment, and the orange node is the client environment. When the server renders a response to the client, the user redirects to the new page without refreshing the front end. Then, the yellow area is used to fetch the page data and deliver it to client_render to render the new page

Environment differentiation 🤔

Homogeneous framework because it is the same set of code, but the environment is different, some objects are not own, we should distinguish between to avoid problems, this project has provided a isServer variable is used to determine the service side environment or the client environment, and we do the drop can also add one more environment determine whether the client rendering mode to distinguish, And we’ll see why we make that distinction.

We need to make three environmental distinctions

1. Server rendering environment;

2. Client environment after rendering on the server;

3. Client rendering environment;

Here’s how I do it:

// isServer is distinguished by node environment specific processes and specific environment variables
export constisServer = !! process&&!! process.env&&(process.env.RENDER_ENV =='server');
// All render environments that are not server-side belong to isClient
export constisClient = ! isServer;// To distinguish degraded client rendering isCSR,CSR does not have the data __PRELOADED_STATE__
export constisCSR = ! isServer && !window.__PRELOADED_STATE__;
Copy the code

Front-screen interface data prefetch 🤓

Cra – SSR is to use the react to frontload to do the first screen of the asynchronous processing, see the file SRC/app/usage routes/profile/index, js, on the server by prefetching data to state management redux of the store, Insert window.__preloaded_state__ as the initial store, take window.__preloaded_state__ as the initial store on the client side, and the connect component gets populated with data.

The client rendering does not prefetch data window.__preloaded_state__, so frontLoad must satisfy the following three conditions:

① Frontload in the server after rendering to the first screen of the client can not repeat the execution of the request

② After the downgrade, the client rendering mode can perform frontload to send requests to pull data and ensure the same writing method as the server

③ After the server rendering to the client after the user click the route jump to the client can request to pull data like the client

To ensure the same writing, server rendering and client rendering must use the same state management store. We need to encapsulate a specific global_state store for sending the first screen data.

import { frontloadConnect } from 'react-frontload';
import {isServer, isCSR} from 'xxx/xxx';
import {change_state, get_state} from 'xxx/global_state';
import {connect} from 'dva';

export const frontload = (frontload_fn) = > {
  return (Target) = > {
    // Inject global_state into the component by default
    return connect(({global}) = > ({global}))(frontloadConnect(async (props) => {
      // get_state('client_load') => True when a client triggers a route jump
      if (get_state('client_load') || isServer || isCSR) {
        await frontload_fn(props)
      }
    })(Target))
  }
}
Copy the code

Using this frontload instead of the original frontloadConnect function not only simplifies writing, but also keeps the same set of code satisfying the above three conditions.

You can write this in the component:

// (This project introduces DVA, Typescript, SCSS and react-CSS-modules)
import * as React from 'react';
import { isCSR } from 'xxx/xxx';
const styles = require('./index.scss');
const CSSModules = require('react-css-modules');
import { frontload } from 'xxx/adapter'; 
import { API } from 'xxx/http';
import {config} from 'src/utils/config';


@frontload(async (props) => {
    const res = await API.get('/getNews');
    // Update store global to unify server and client writing.
    props.dispatch({type: 'global/set'.payload: {data_list: res.data}})
})
@CSSModules(styles)
export default class News extends React.PureComponent<any.any>{
    constructor(props){
        super(props);
        this.state = {
            news_list: [], 
        }
    }
    componentDidMount() {
        this.setState({
            // Global data is populated with frontload prefetch data and the current component is automatically connected
            // So the component does not need to connect to access the global data
            news_list: this.props.global.data_list,
        })
    }
    render() {
        const news_list = this.props.global.data_list;
        return (
          news_list.map((item) = > {
            return <section>
              <p>Title: {item. The title}</p>
              <p>Abstract: {item. The abstract}</p>
            </section>}}}))Copy the code

Add client render startup server 😎

Also add a server file to start client rendering, such as csr_server.js

var path = require('path')
var express = require('express')
var compression = require('compression')
var app = express()
var data = require('./data_config.json');
app.use(compression());
// The built resources folder
app.use(express.static(path.join(__dirname, '.. /dist/')));

port = 8080
app.listen(port, function () {
  console.log('The app server is working at ' + port)
})
Copy the code

These are some of the steps I’ve taken to modify the server-side isomorphic framework degradation, but I need to understand it myself, and adjust it according to my own project needs (for example, my project also introduced DVA, typescript, etc.)

This article is only in front of the point of view of downgrade, as for the specific server how to downgrade, from the perspective of operation and maintenance to see what rules are suitable for downgrade, how to downgrade these problems, will not be described in this article.