How to start developing with the Nextjs rating not rated
SoftXML
                  Programming Books
                Open Web Directory
           SoftXMLLib | SoftEcartJS
xml products   Home page    contact    site map
Contact Us
Latest Blog Articles:   Latest SEO Articles | RSS Feed
DOM manipulation best practices
JavaScript Module Pattern
How to start developing with the Nextjs
Angular application structure
Write Guest Articles
Articles Archive




Loading...

The Art & Science of JavaScript ($29 Value FREE For a Limited Time)
The Art & Science of JavaScript ($29 Value FREE For a Limited Time)










How to start developing with the Nextjs


Building React Apps with Server-Side Rendering

Back in 2013, Spike Brehm from Airbnb published an article in which he analyzed the shortcomings of SPA applications (Single Page Application) and, as an alternative, proposed a model of isomorphic web applications. Now the term universal web application is used more often.

In a universal web application, each page can be formed either by a web server or by JavaScript on the client side. At the same time, the source code of the programs that are executed by the web server and the client should be unified (universal) in order to eliminate inconsistency and increased development costs.

Criticism of SPA applications

What is wrong with SPA applications? And what problems arise when developing universal applications?

SPA applications are criticized, first of all, for their low search engine ranking (SEO), speed of work, and accessibility. There is evidence that React applications may not be available for screen readers.

Partially, the issue with SEO SPA-applications is solved by Prerender - a server with a “headless” client, which is implemented using chrome-remote-interface (previously used by phantomjs). You can deploy your own server with Prerender or access a public service. In the latter case, access will be free with a limit on the number of pages. The process of generating a page using Prerender tools is time-consuming - usually more than 3 seconds, which means that search engines will consider such a service not optimized in speed, and its rating will still be low.

Performance problems may not appear during the development process and become noticeable when working with low-speed Internet or on a low-power mobile device (for example, a phone or tablet with 1GB of RAM and a processor frequency of 1.2GHz). In this case, the page that “flies” may load unexpectedly long.

There are more reasons for such a slow download than usually indicated. First, let`s see how the application loads JavaScript. If there are a lot of scripts (which was typical when using require.js and amd-modules), then the loading time increased due to the overhead of connecting to the server for each of the requested files.

The solution was obvious: combine all the modules into one file (using rjs, webpack or another linker). This caused a new problem: for a web application with a rich interface and logic, when loading the first page, all JavaScript code loaded into a single file. Therefore, the current trend is code splitting.

Libraries for creating universal applications

On github.com, you can now find a large number of projects that implement the idea of a universal web application. However, all these projects have common disadvantages:

  1. small number of project contributors
  2.                 
  3. these are draft projects for a quick start, not a library
  4.                 
  5. projects were not updated when new versions of react.js were released
  6.                 
  7. in projects, only part of the functionality necessary for the development of a universal application is implemented.

The first successful solution was the Next.js library, which has 46,500 “stars” on github.com. To evaluate the advantages of this library, we will consider what kind of functionality you need to provide for a universal web application.

Server rendering

Libraries such as react.js, vue.js, angular.js, riot.js and others all support server-side rendering. Server rendering usually works synchronously. This means that asynchronous API requests in life cycle events will be launched, but their result will be lost. (Limited support for asynchronous server rendering is provided by riot.js)

Asynchronous data loading

In order for the results of asynchronous requests to be obtained before the start of server rendering, Next.js implements a special type of component “page”, which has an asynchronous life cycle event - static async getInitialProps ({req}).

Passing server component state to client

As a result of the server rendering of the component, an HTML document is sent to the client, but the state of the component is lost. To transfer the state of a component, usually the web server generates a script for the client, which writes the state of the server component to the global JavaScript variable.

Creation of a component on the side of a client and its binding to an HTML document

The HTML document resulting from server-side rendering of the component contains text and does not contain components (JavaScript objects). Components must be recreated in a client and “tied” to the document without re-rendering. In react.js, the hydrate() method is executed for this. A similar function method is in the vue.js library.

Routing

Routing on the server and on the client should also be universal. That is, the same definition of routing should work for both server and client code.

Code splitting

For each page, only the necessary JavaScript code should be loaded, and not the entire application. When moving to the next page, the missing code should be loaded - without reloading the same modules, and without extra modules.

The Next.js library successfully solves all these problems. This library is based on a very simple idea. It is proposed to introduce a new type of component - “page”, in which there is an asynchronous method static async getInitialProps ({req}). A page component is a regular React component. This type of component can be thought of as a new type in the series: “component”, “container”, “page”.

Working example

To work, we need node.js and the npm package manager. If they are not already installed, the easiest way to do this is with nvm (Node Version Manager), which is installed from the command line and does not require sudo access:


            curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
        

After installation, be sure to close and reopen the terminal to set the PATH environment variable. The list of all available versions is displayed by the command:


            nvm ls-remote
        

Download the required version of node.js and a compatible version of the npm package manager with the command:


            nvm install 8.9.4
        

Create a new directory (folder) and execute the command in it:


            npm init
        

As a result, the package.json file will be generated. Download and add the necessary dependensies of the project:


            npm install --save axios next next-redux-wrapper react react-dom react-redux redux redux-logger
        

In the project`s root directory, create the pages directory. This directory will contain page-type components. The path to the files inside the pages directory corresponds to the url by which these components will be available. As usual, the “magic name” index.js is mapped to url /index and /. More complex rules for urls with wildcard are also implementable.

Create file the pages/index.js:


            import React from "react"
            export default class extends React.Component {
                static async getInitialProps({ req }) {
                    const userAgent = req ? req.headers["user-agent"] : navigator.userAgent
                    return { userAgent }
                }
                render() {
                    return (
                    
Hello World {this.props.userAgent}
) } }

This simple component uses the main features of Next.js:

  • es7 syntax is available (import, export, async, class) out of the box.
  • Hot-reloading also works out of the box.
  • The static async getInitialProps ({req}) function will be executed asynchronously before rendering the component on the server or on the client — and only once. If the component is rendered on the server, the req parameter is passed to it. The function is called only on page-type components and is not called on nested components.

In the package.json file, add three commands to the “scripts” attribute:


            "scripts": {
                "dev": "next",
                "build": "next build",
                "start": "next start"
            }
        
        

Start the developer server with the command:


            npm run dev
        
        

To implement the transition to another page without loading the page from the server, the links are wrapped in a special Link component. Add a dependency to page/index.js:


            import Link from "next/link"
        
        

and Link component:


            <Link href="/time">
                <a>Click me</a>
            </Link>
        
        

When you click on the link, a page with a 404 error will be displayed.

Copy the pages/index.js file to the pages/time.js file. In the new time.js component, we will display the current time received asynchronously from the server. In the meantime, change the link in this component so that it leads to the main page:


            <Link href="/">
                <a>Back</a>
            </Link>
        
        

Try several times to reload each of the pages from the server, and then go from one page to another and return back. In all cases, downloading from the server will take place with server rendering, and all subsequent transitions will be done with rendering tools on the side of the client.

On the pages/time.js page, we will place a timer that shows the current time received from the server. This will allow you to get acquainted with asynchronous data loading during server rendering.

We use redux to store data in the store. Asynchronous actions in redux are performed using middleware redux-thunk. Usually (but not always), one asynchronous action has three states: START, SUCCESS FAILURE. Therefore, the code for determining the asynchronous action often looks complicated.

In one issue of the redux-thunk library, a simplified version of middleware was discussed, which allows you to define all three states on one line. Unfortunately, this option was never issued to the library, so we will include it in our project as a module.

Create a new redux directory in the root directory of the application, and in it file redux/promisedMiddlewate.js:


            export default(...args) => ({ dispatch, getState }) => (next) => (action) => {
                const { promise, promised, types, ...rest } = action;
                if (!promised) {
                  return next(action);
                }
                if (typeof promise !== "undefined") {
                  throw new Error("In promised middleware you mast not use "action.promise");
                }
                if (typeof promised !== "function") {
                  throw new Error(`In promised middleware type of "action"."promised" must be "function"`);
                }
                const [REQUEST, SUCCESS, FAILURE] = types;
                next({ ...rest, type: REQUEST });
                action.promise = promised()
                  .then(
                    data => next({ ...rest, data, type: SUCCESS }),
                  ).catch(
                    error => next({ ...rest, error, type: FAILURE })
                  );
              };
        
        

A few clarifications on how this function works. The redux midleware function has the signature (store) => (next) => (action). The indicator that the action is asynchronous and should be processed by this particular function is the promised property. If this property is not defined, then processing is completed and control is transferred to the following middleware: return next (action). A reference to the Promise object is saved in the action.promise property, which allows you to “hold” the static async getInitialProps ({req, store}) asynchronous function until the asynchronous action is completed.

All that is connected with the data warehouse will be placed in the redux/store.js file:


            import { createStore, applyMiddleware } from "redux";
            import logger from "redux-logger";
            import axios from "axios";
            import promisedMiddleware from "./promisedMiddleware";
            const promised = promisedMiddleware(axios);
            export const initStore = (initialState = {}) => {
            const store = createStore(reducer, {...initialState}, applyMiddleware(promised, logger));
            store.dispatchPromised = function(action) {
                this.dispatch(action);
                return action.promise;
            }
            return store;
            }
            export function getTime(){
                return {
                promised: () => axios.get("http://time.jsontest.com/"),
                types: ["START", "SUCCESS", "FAILURE"],
                };
            }
            export const reducer = (state = {}, action) => {
            switch (action.type) {
                case "START":
                return state
                case "SUCCESS":
                return {...state, ...action.data.data}
                case "FAILURE":
                return Object.assign({}, state, {error: true} )
                default: return state
            }
            }
        
        

The getTime() action will be processed by promisedMiddleware(). For this, the promised property has a function that returns Promise, and the types property has an array of three elements containing the constants ‘START’, ‘SUCCESS’, ‘FAILURE’. The values of constants can be arbitrary, their order in the list is important.

Now it remains to apply these actions in the pages/time.js component:


            import React from "react";
            import {bindActionCreators} from "redux";
            import Link from "next/link";
            import { initStore, getTime } from "../redux/store";
            import withRedux from "next-redux-wrapper";
            function mapStateToProps(state) {
            return state;
            }
            function mapDispatchToProps(dispatch) {
            return {
                getTime: bindActionCreators(getTime, dispatch),
            };
            }
            class Page extends React.Component {
            static async getInitialProps({ req, store }) {
                await store.dispatchPromised(getTime());
                return;
            }
            componentDidMount() {
                this.intervalHandle = setInterval(() => this.props.getTime(), 3000);
            }
            componentWillUnmount() {
                clearInterval(this.intervalHandle);
            }
            render() {
                return (
                <div>
                    <div>{this.props.time}</div>
                        <div>
                            <Link href="/">
                                <a>Return</a>
                            </Link>
                        </div>
                    </div>
                )
            }
            }
            export default withRedux(initStore, mapStateToProps, mapDispatchToProps)(Page);
        
        

Tag cloud

Rate This Article
(votes 2)

No Comments comments

Post Comment

We love comments on this blog - they are as important as anything we write ourself. They add to the knowledge and community that we have here. If you want to comment then you�re more than welcome � whether you feel you are a beginner or an expert � feel free to have you say.



* = required
Leave a Reply
Name *:
Email *
(will not be published):
Website:
Comment *:
Human Intelligence Identification *:
What is the background color of this web page?
  
Close
Please enter a valid email Please enter a valid name Please enter valid email Please enter valid name Enter valid year
™SoftXML.   Privacy Statement  |  Article Archive  |  Popular Web Development Books
^Top