โš› ๏ธ React is the front one of the most popular UI library community, its development based on modular approach greatly improved the front end development experience, the React with the application of split a large to small components, to make our code more can be reused, and better maintainability, and so on has many other advantages…

Part II Version portal

React creates a single page application (SPA). A single page application has a better user experience on the browser side than a traditional web page. The browser usually takes an HTML with an empty body and loads the js specified by the script. Start executing js, then apply colours to a drawing to the dom, in this process, normal users can only wait for, can’t do anything, if the user is in a high-speed network, equipment configuration, high above the first to load all the js and then execute process may not be much of a problem, but there are a lot of things is our Internet connection, The device may not be the best, in which case a one-page app may be a poor user experience, and users may leave the site before they experience the benefits of a browser SPA, in which case your site won’t get many page views no matter how well done it is.

But we can’t go back to the previous one page a page of traditional development, modern UI library provides the service side rendering (SSR) function, make our development of SPA applications can run on the server, greatly accelerate the first screen rendering time, so the user can see the contents of the web pages faster at the same time, After loading, all DOM events and various interactions are added to the page. Finally, it runs in the form of a SPA. In this way, we can not only improve the first screen rendering time, but also get SPA client user experience, which is a necessary function for SEO ๐Ÿ˜€.

BTW, now that we are in an async/await environment, we are using KOA2 for server rendering.

Initialize a normal one-page application SPA

First of all, let’s ignore the renderers on the server. We will create a SPA based on React and React-Router first. After creating a complete SPA, we will add SSR function to maximize the performance of the app.

First enter the app entry app.js:

import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom';

const Home = () => <div>Home</div>;
const Hello = () => <div>Hello</div>;

const App = () => {
  return (
    <Router>
      <Route exact path="/" component={Home} />
      <Route exact path="/hello" component={Hello} />
    </Router>
  )
}

ReactDOM.render(<App/>, document.getElementById('app'))
Copy the code

Above we created two components for routing/and /hello that just render some text to the page. But when our project gets bigger and bigger, and we have more and more components, the JS that we package can get really big and uncontrollable, so the first thing we need to optimize is code-splitting. Fortunately, this is easy to do with webpack Dynamic import and react-loadable.

Use react-loadable to split the time code

Install react-loadable before using it:

npm install react-loadable
# or
yarn add react-loadable
Copy the code

Then in your javascript:

/ /...
import Loadable from 'react-loadable';
/ /...

const AsyncHello = Loadable({
  loading: <div>loading...</div>.// Write your Hello component to a separate file
  // Then use webpack's dynamic import
  loader: (a)= > import('./Hello'})),// Then use loadable wrapped components in your route:
<Route exact path="/hello" component={AsyncHello} />
Copy the code

The loading option is to render the loading component when loading the JS required by the Hello component dynamically. The loading option gives the user a feeling that the js is being loaded, and the experience is better than nothing.

Now, if we visit the Home page, only the JS that the Home component depends on will be loaded. If we click on a link to the Hello page, the loading component will be rendered, and the Loading component will be asynchronously loaded. After loading, the loading component will be replaced by the loading component to render the Hello component. Our SPA has been greatly optimized by splitting code into different code blocks based on routing, cheers๐Ÿป. React-loadable also supports SSR, so you can use react-loadable anywhere, either on the front end or on the server side. There are some additional configurations that need to be done for react-loadable to run properly on the server side, which will be discussed later in this article.

We have already created a basic React SPA and split the code. Our app already has good performance, but we can still optimize the performance of the app to the utmost. Next, we add SSR function to further improve the loading speed, and solve the SEO problem in SPA by the way ๐ŸŽ‰.

Added server-side rendering (SSR) functionality

Let’s start with the simplest KOA Web server:

npm install koa koa-router
Copy the code

Then in the entry file app.js for KOA:

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();
router.get(The '*'.async (ctx) => {
  ctx.body = ` 
         
       React SSR   
      
`
; }); app.use(router.routes()); app.listen(3000.'0.0.0.0'); Copy the code

We render this HTML by default, including javascript packaged within the HTML. You can also use some server-side template engines (e.g. Nunjucks) to render HTML files directly, using htML-webpack-plugin to automatically insert packed JS/CSS resource paths when webpack is packaged.

We need to use StaticRouter instead of BrowserRouter, because on the server, the route is static. This will not work with BrowserRouter, and there will be some configuration later to make react-Loadable run on the server.

Tip: You can write the entire Node side of the code in ES6/JSX style instead of part commonJS and part JSX, but then you need to compile the entire server side of the code in Webpack to commonJS style to make it run in node environment. Here we’ll separate out the React SSR code and require it in the normal Node code. Because it may be that in an existing project, where the commonJS style is used before, converting node code to ES6 is a bit costly, but can be migrated step by step later

OK, now in your appssr.js:

import React from 'react';
// Use a static router
import { StaticRouter } from 'react-router-dom';
import ReactDOMServer from 'react-dom/server';
import Loadable from 'react-loadable';
// This is what you need to make react-loadable work on the server, which is covered below
import { getBundles } from 'react-loadable/webpack';
import stats from '.. /build/react-loadable.json';

// The react-router route is removed so that it can be shared between the browser and the server
// The following will also cover...
import AppRoutes from 'src/AppRoutes';

// Here we create a simple class, expose some methods, and call them in the KOA route to implement server-side rendering
class SSR {
  // This method is called in the KOA route
  render(url, data) {
    let modules = [];
    const context = {};
    consthtml = ReactDOMServer.renderToString( <Loadable.Capture report={moduleName => modules.push(moduleName)}> <StaticRouter location={url} context={context}> <AppRoutes initialData={data} /> </StaticRouter> </Loadable.Capture> ); // getBundles = getBundles(stats, modules); return { html, scripts: this.generateBundleScripts(bundles), }; GenerateBundleScripts (bundle) {return bundle. Filter (bundle => bundle) {return bundle. bundle.file.endsWith('.js')).map(bundle => { return `<script type="text/javascript" src="${bundle.file}"></script>\n`; }); } static preloadAll() { return Loadable.preloadAll(); } } export default SSR;Copy the code

When compiling this file, use target in the Webpack configuration: “Node” and externals. In addition, you need to add the react-Loadable plugin to the webpack configuration of your front-end app. App packaging should be run before SSR packaging, otherwise you will not get the component information required by React-Loadable. Let’s start with app packaging:

//webpack.config.dev.js, app bundle
const ReactLoadablePlugin = require('react-loadable/webpack')
  .ReactLoadablePlugin;

module.exports = {
  / /...
  plugins: [
    / /...
    new ReactLoadablePlugin({ filename: './build/react-loadable.json',]}})Copy the code

Add loadable plugin to.babelrc:

{
  "plugins": [
      "syntax-dynamic-import"."react-loadable/babel"["import-inspector", {
        "serverSideRequirePath": true}}]]Copy the code

This configuration lets react-Loadable know which components are finally rendered on the server side, and then inserts them directly into the HTML script tag. It also takes the SSR components into account during front-end initialization to avoid reloading.

//webpack.ssr.js
const nodeExternals = require('webpack-node-externals');

module.exports = {
  / /...
  target: 'node'.output: {
    path: 'build/node'.filename: 'ssr.js'.libraryExport: 'default'.libraryTarget: 'commonjs2',},// Avoid packing all the libraries in node_modules, the SSR JS will run directly on the node side,
  // So it doesn't need to be packaged into the final file, it will be loaded automatically from node_modules at runtime
  externals: [nodeExternals()],
  / /...
}
Copy the code

Then in koa app.js, require it and call the SSR method:

/ /... koa app.js
//build out the ssr.js
const SSR = require('./build/node/ssr');
// Preload all components on server side
SSR.preloadAll();

// Instantiate an SSR object
const s = new SSR();

router.get(The '*'.async (ctx) => {
  // Render different page components according to the route
  const rendered = s.render(ctx.url);
  
  const html = ` <! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> </head> <body> <div id="app">${rendered.html}</div>
        <script type="text/javascript" src="/runtime.js"></script>
        ${rendered.scripts.join()}
        <script type="text/javascript" src="/app.js"></script>
      </body>
    </html>
  `;
  ctx.body = html;
});
/ /...
Copy the code

Rasterized scripts, which contain script labels made up of SSR components, will be rendered on the server by act-LOADable. Rasterized scripts will be rendered on the server by act-Loadable, rendered on a server basis, and rendered on a server basis. Call SSR#generateBundleScripts() method to make sure script tags are after runtime.js (fetched by CommonsChunkPlugin) And you should know which components have been rendered before the App Bundle is initialized. For more information about react-loadable server support, see here.

We have also removed the React-Router route so that it can run in the browser and server. Here is the AppRoutes component:

//AppRoutes.js
import Loadable from 'react-loadable';
/ /...

const AsyncHello = Loadable({
  loading: <div>loading...</div>.loader: (a)= > import('./Hello'})),function AppRoutes(props) {
  <Switch>
    <Route exact path="/hello" component={AsyncHello} />
    <Route path="/" component={Home} />
  </Switch>} export default AppRoutes // import AppRoutes from './AppRoutes'; / /... export default () => { return (<Router>
      <AppRoutes/>
    </Router>)}Copy the code

The initial state of server rendering

So far, we have created a React SPA that runs ๐Ÿบ on the browser side with the server side. The community calls it the Universal app or Isomophic App. Generally speaking, the data or state of our app needs to be obtained asynchronously through the remote API, and then we can start rendering components. The same is true for SSR on the server. We need to dynamically obtain the initial data, and then we can throw it to React for SSR. Then on the browser side we also need to initialize so that we can synchronize the initialization data of these SSR to avoid the re-acquisition during the browser side initialization.

Here we simply get some project information from Github as page initialization data in koA app.js:

/ /...
const fetch = require('isomorphic-fetch');

router.get(The '*'.async (ctx) => {
  //fetch branch info from github
  const api = 'https://api.github.com/repos/jasonboy/wechat-jssdk/branches';
  const data = await fetch(api).then(res= > res.json());
  
  // Pass in initialization data
  const rendered = s.render(ctx.url, data);
  
  const html = ` <! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> </head> <body> <div id="app">${rendered.html}</div>
        
        <script type="text/javascript">window.__INITIAL_DATA__ = The ${JSON.stringify(data)}</script>
        
        <script type="text/javascript" src="/runtime.js"></script>
        ${rendered.scripts.join()}
        <script type="text/javascript" src="/app.js"></script>
      </body>
    </html>
  `;
  ctx.body = html;
});
Copy the code

Then in your Hello component, you need to check whether there is a window in the checkWindow (or check in the App entry and pass it to the props sub-component).__INITIAL_DATA__ is used as the initial data if there is one. If not, let’s go back to the componentDidMount lifecycle function:

export default class Hello extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      // If the parent component is passed in, check the props
      github: window.__INITIAL_DATA__ || [],
    };
  }
  
  componentDidMount() {
    // If there is no data, then request data again
    // The method that requests data can also be removed so that the browser and server can call it together and avoid repeated writes
    if (this.state.github.length <= 0) {
      fetch('https://api.github.com/repos/jasonboy/wechat-jssdk/branches')
        .then(res= > res.json())
        .then(data= > {
          this.setState({ github: data });
        });
    }
  }
  
  render() {
    return (
      <div>
        <ul>
          {this.state.github.map(b => {
            return <li key={b.name}>{b.name}</li>;
          })}
        </ul>
      </div>); }}Copy the code

Ok, now if the page has been rendered by the server, the browser will get all the rendered HTML, including the initialization data, and then through the SSR content with loaded JS, and then form a complete SPA, just like a normal SPA, but we get better performance, better SEO๐Ÿ˜Ž.

๐ŸŽ‰ React – v16 updates

In the latest version of React, V16, the SSR API has been greatly optimized and a new stream-based API has been added to improve performance. The Streaming API allows servers to render HTML while sending it to the browser. The browser can also start rendering pages in advance rather than wait for all server components to complete rendering before initiating browser initialization, which improves performance and reduces server resource consumption. Another thing to note on the browser side is the need to use reactdom.hydrate () instead of reactdom.render (), Whats more, see Medium article Whats new-with-server-side-rendering in-react-16.

๐Ÿ’– to see the full demo, see Koa-Web-Kit, a modern React/ KOA-based full-stack development framework that includes React SSR support and can be used directly to test server rendering capabilities ๐Ÿ˜€

conclusion

Ok, so that’s the simple practice of React-SSR + Koa. Through SSR, we Both improved performance and satisfied SEO requirements, Best of the Both Worlds๐Ÿบ.

PPT in Browser

English Version