Next.js setup step by step

We’ve already presented you Next.js integration with RSSR performance and features so now it’s the time to take a little practice and start working with the framework. You can use the project on Merixstudio's GitHub as training to learn how to set up and expand an app. Actually, it was made for this purpose - we prepared it for one of the workshops introducing SSR with Next.js. We used a public API and its configuration is very simple so you can put more attention to properly set the project. The whole UI was built with Semantic UI React. The best way to get familiar with the project is to analyze the commits from the very beginning so you can easily check how the app was made. Every commit adds small features or iterates the ones which already exist. 

The app is divided into 3 parts: 

  • Index - includes a form which enables to write down a searched phrase and to set the proper filters. While sending the form you need to redirect to search results view in which you can set some of the parameters as a query string in a URL;       
  • Search - in getInitialProps access searched phrase and search format from query string. Then perform an action to load data from API and render a table with results.  
  • Card - the view of a single entry’s detail. In getInitialProps, you need to get a card’s ID and send a request with it to the API. 
  • Afterward, view the card’s details. 

Furthermore, in the app, we can find: 

  • Layout - a component created to set an appropriate layout for every page; 
  • Actions, reducers, store - a whole Redux configuration set to support Next.js. Additionally, the _app component is wrapped in HOC withRedux from a next-redux-wrapper package. 

How to implement user authentication properly in Next.js?

Every app needs a suitable user authentication system and checks if a user has proper permissions to access a particular view. In React apps which doesn’t use SSR in a typical way, the solution is to write Higher Order Component or other helper function. It will check if a proper token, which needs to be added to the header of every request, exists (e.g. in cookies or localStorage). If it doesn’t and there will be an attempt to visit the website only for logged in users then a redirection to login page will be automatically performed. If the existing token is out-of-date or the user doesn’t have appropriate permissions then it will only take to check the status of the request in API and to react adequately. 

In the case of using SSR and Next.js, it’s a little bit more complicated because the browser sends the initial request to the Next.js server and each next one is sent directly to the API. There are several problems showing up and you need to solve them:  

  1. In getInitialProps you should check if the code is performed by a browser or a server (code has to be universal).
  2. If the query is served by a server you need to find a way to get the user’s token
  3. While sending a query to API from a server side you need to consider catching errors and performing proper actions depending on the request status code. 

For the purposes of this article, we assume using JWT and that a user is already logged in and has the proper token kept in the cookies. Additionally, the whole app will use Redux. 

The first step for user authentication service is to create a higher order component in which you should check if specific conditions have been met and if we can show a particular page to the user. Within Next.js 6.0 the ability to overwrite default App component appeared and it’s a great location to place such logic there. 

We will now present an approach using HOC which can be easily moved to App. During the process of defining the view components in pages we would like to perform it like this:  

import React from 'react';

import BasePage from '@shared/base/BasePage';
import Profile from '@routes/Profile/containers/Profile';

class ProfilePage extends React.Component {
  render() {
return <Profile />;
  }
}

export default BasePage(ProfilePage, 'Private'); 

The code above brings to a conclusion that the goal is to create HOC, which besides the component will provide information about for whom the page will be available. This HOC will pack the Profile component in a suitable page structure so now let’s create an elementary BasePage:

export default (ChildComponent, permission = 'Public') => class extends React.Component {
  static async getInitialProps(context) {
    const { req, res, store } = context;
    let initProps = {
      isServer: !!req,
  isLoggedIn: (!!req && !!req.cookies.token) || store.getState().auth.me.id,
    };

    return initProps;
  }

  render() {
    return (
  <Layout>
      <ChildComponent {...this.props} />
  </Layout>
    );
  }
};

We will extend this code with a proper logic but now we will analyze it line after line. To begin we defined the function taking a component as a first parameter and as the second - website’s accessibility (by default set as Public). Pages, which weren’t provided with this parameter will be available for all users. The mentioned function returns a React component. You need to remember that the Higher Order Component is nothing else than a variation on Higher Order Function, which returns or receives function as a parameter, and React components are exactly that, just functions! In this case, unnamed class (as classes are functions too!) will be returned and they will extend the React Component. In accordance with the Next.js requirements, if you want to perform asynchronous operations you have to define a getInitialProps static method, just like in every view included in the pages catalog.  In this function, we define our initial properties (initProps). 

In which moment a user is being logged in? 

  • If it’s an initial query and it’s served by Node, then you need to check if there’s a token in this request’s cookies (we save JWT token in cookies as a token). Remember that if you want to refer to cookies in the request, you have to extend server.js and add a cookie parser there or turn the character string into an interactive object manually. 
  • If it’s an asynchronous request sent by a browser, then check if the users object exist in a store (assuming that you use Redux) or you’re able to verify if a user is logged in in some other way. 

Now, when everything is ready, we can start to consider different cases of page’s availability and the user’s status. Let’s check the first example - the page is accessible only for logged in users:   

if (permission === 'Private' && !initProps.isLoggedIn) {
  res.writeHead(302, { Location: '/login' });
  res.end();
  res.finished = true;
}

If the page should be available only for logged in users and according to above requirements it’s impossible to define it, you need to perform a redirection to a login page. Then we call the end of the request function. Why we use res.end and set res.finished? It’s compatible with the example in wiki Next.js but on the other hand, Node.js documentation says, that finished will be set in the truth when the end function will be executed. This attribute is poorly documented but in our case, we followed the example set by Next.js.

Now, let’s deal with the situation when the page is accessible for unlogged users:

 if (permission === 'GuestOnly' && initProps.isLoggedIn) {
   res.writeHead(302, { Location: '/home' });
   res.end();
   res.finished = true;
 }

The procedure is analogous to Private page case. Then, if the user met specific requirements, you need to consider the situation when the initial query (the code is served by Node) and the user is logged in. It means that token exists but the user hasn’t been verified yet and the token correctness is not yet checked.  

if (initProps.isServer && initProps.isLoggedIn) {
  request.setAuthToken(req.cookies.token);
   try {
     await store.dispatch(getMe());
   } catch (error) {
     res.writeHead(302, { Location: '/logout' });
     res.end();
     res.finished = true;
   }
 }

Firstly add the token to the query header (you can use supporting function setAuthToken which was prepared earlier) according to JWT requirements. The next step is to perform Redux action which is responsible for executing a proper query to API and - when it will succeed - saving user’s data in a store under auth.me (that’s why we need to check if the user was logged in within store.getState().auth.me.id). If for some reason an error appeared while fetching user data, it might mean that the token expired or is incorrect. In this case, you have to perform redirection to logout page which should remove the token from cookies, add a header to each query and redirect a user to the login page. In the end, after meeting all requirements and checking if the user is able to visit a particular page, execute getInitialProps of page’s component (ChildComponent).  

 if (typeof ChildComponent.getInitialProps === 'function') {
   initProps = {
    ...initProps,
     ...await ChildComponent.getInitialProps(context),
   };
 }

The code above is very similar to the one implemented in the place the App is overwritten. To sum up - the whole HOC code should look like this: 

import React from 'react';

import request from '@utils/request';
import { getMe } from '@store/actions/auth';
import Layout from '@shared/layout/Layout';

export default (ChildComponent, permission = 'Public') => class extends React.Component {
 static async getInitialProps(context) {
   const { req, res, store } = context;
   let initProps = {
     isServer: !!req,
     isLoggedIn: (!!req && !!req.cookies.token) || store.getState().auth.me.id,
     statusCode: 200,
   };
   if (permission === 'Private' && !initProps.isLoggedIn) {
     res.writeHead(302, { Location: '/login' });
     res.end();
     res.finished = true;
   }
   if (permission === 'GuestOnly' && initProps.isLoggedIn) {
     res.writeHead(302, { Location: '/home' });
     res.end();
     res.finished = true;
   }
   if (initProps.isServer && initProps.isLoggedIn) {
     request.setAuthToken(req.cookies.token);
     try {
       await store.dispatch(getMe());
     } catch (error) {
       res.writeHead(302, { Location: '/logout' });
       res.end();
       res.finished = true;
     }
   }
   if (typeof ChildComponent.getInitialProps === 'function') {
     initProps = {
       ...initProps,
       ...await ChildComponent.getInitialProps(context),
     };
   }

   return initProps;
 }

 render() {
   return (
     <Layout
       statusCode={this.props.statusCode}
     >
       <ChildComponent {...this.props} />
     </Layout>
   );
 }
};

This example illustrates the problems which you can encounter while implementing SSR with  Next.js and shows example solutions for one of the most frequently implemented features in each app. Remember that it’s necessary to write a code that will be universal (isomorphic)!

Next.js - easy to set up, powerful for SSR tasks

Creating web apps might sometimes be overwhelming - there are a number of solutions available on the market and it’s getting more and more common to implement a part of business logic on the frontend side of the app. Discovering all the elements and additional libraries needed to build a fully-fledged app takes a lot of time and it might unnecessarily complicate the project’s architecture. And SSR should be considered as one of the aspects which can lead to this kind of problems. If we will make a decision about choosing the right solution quickly, the rendering implementation on the server-side might turn out to be easy and pleasant. 

In our project, we also started to use Next.js which automatically sped up working with SSR. Before that we had to create all the solutions provided by the framework on our own and in the situation when the client wanted to use a particular technology on the further stage of developing the app, often took too much time. We needed to check the whole app again to avoid errors. Next.js provides the ability to build the website adjusted to SSR so any problem can be noticed immediately and we don’t repeat erroneous solutions on another page. 

Even though Next.js is a minimalistic framework it’s also flexible and extendable. Running the React app with it is fast and the setup is clear and well-documented. The framework is still being intensively developed and supported by the community. A few features, which we mentioned in the article, were added just in recent versions (like the ability to overwrite App.js). And what is most essential - it’s just perfect to manage SSR tasks. 

The article was originally released in polish language on justgeek.it  (part Ipart II). 

Like the way our frontend devs work? Join them! Check Merixtudio's job offers.  

Navigate the changing IT landscape

Some highlighted content that we want to draw attention to to link to our other resources. It usually contains a link .