Convert react app to server side rendering with next.js

Last updated : March 23, 2021

This article explains how to convert a react js application to Next js to take advantage of the server-side rendering that Next js offers. One of the main reasons to server-side render a react app is to improve SEO performance and page load times.

Client-side rendering (csr) vs. Server-side rendering (ssr)

As its name implies, in client-side rendering, the browser is responsible for rendering the page with the resources it receives from the server. Your browser receives a server response with a bare-bones HTML document with JavaScript and CSS files. Therefore, the browser has to wait until all the resources are downloaded and then render the page. Until these steps are complete, all you see is a blank page. Usually, in CSR, this first request to the server is resource-intensive and time-consuming, impacting the page load performance negatively.

On the other hand, in SSR, the server does most of the heavy work and responds with a compiled Html document to the browser. The server pre-renders the Html and very little to no processing in the browser. As a result, the browser does less work and receives fewer resources in every request.

The problem with client-side rendering

As I discussed above, initial page load performance is one of the main drawbacks of CSR apps. The other significant factor is the SEO friendliness of the rendered pages. I discuss this topic below.

If your application is a public-facing website and SEO matters, you may consider rendering the app server side.

Create a simple react app

Now, let's create a simple react app that uses a public API to fetch data.

C:\learnbestcoding> npx create-react-app reactapp
C:\learnbestcoding> cd reactapp
C:\learnbestcoding\reactapp> npm start

The react app consists of two pages, Users and Posts. It also has a top menu to navigate between pages. Listed below are the files that assemble the react app.

package.json

{
  "name": "reactapp",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "4.0.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

App.css

.topnav {
  background-color: #333;
  overflow: hidden;
}
.topnav a {
  float: left;
  color: #f2f2f2;
  text-align: center;
  padding: 14px 16px;
  text-decoration: none;
  font-size: 17px;
}
.topnav a:hover {
  background-color: #4CAF50;
  color: black;
}
.topnav a.active {
  background-color: #4CAF50;
  color: white;
}
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}
td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}
tr:nth-child(even) {
  background-color: #dddddd;
}

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

App.js

import './App.css';
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import Users from './Users'
import Posts from './Posts'
const App = () => {
  return (
    <BrowserRouter>
      <React.Fragment>
        <Route path="/" component={Users} exact />
        <Route path="/posts" component={Posts} exact />
      </React.Fragment>
    </BrowserRouter>
  );
}
export default App;

TopMenu.js

import React from 'react';
import { Link } from 'react-router-dom';
const TopMenu = () => {
    return (
        <div class="topnav">
            <Link to='/'>Users</Link>
            <Link to='/posts'>Posts</Link>
        </div>
    );
}
export default TopMenu;

Users.js

import React, { useEffect, useState } from 'react';
import TopMenu from './TopMenu';
const Users = () => {
    const [users, setUsers] = useState([]);
    useEffect(() => {
        fetch('https://jsonplaceholder.typicode.com/users')
            .then(res => res.json())
            .then(data => {
                setUsers(data)
            })
    }, []);
    return (
        <>
            <TopMenu />
            <table>
                <tr>
                    <th>Name</th>
                    <th>User Name</th>
                    <th>Email</th>
                </tr>
                {users.map((user, index) => (
                    <tr>
                        <td>{user.name}</td>
                        <td>{user.username}</td>
                        <td>{user.email}</td>
                    </tr>
                ))}
            </table>
        </>
    );
}
export default Users;

Posts.js

import React, { useEffect, useState } from 'react';
import TopMenu from './TopMenu';
const Posts = () => {
    const [users, setUsers] = useState([]);
    useEffect(() => {
        fetch('https://jsonplaceholder.typicode.com/posts')
            .then(res => res.json())
            .then(data => {
                setUsers(data)
            })
    }, []);
    return (
        <>
            <TopMenu />
            <table>
                <tr>
                    <th>User</th>
                    <th>Title</th>
                    <th>Description</th>
                </tr>
                {users.map((user, index) => (
                    <tr>
                        <td>{user.userId}</td>
                        <td>{user.title}</td>
                        <td>{user.body}</td>
                    </tr>
                ))}
            </table>
        </>
    );
}
export default Posts;

React js project structure

Below is our React js project structure illustrated in the Visual Studio Code. Note that the node_modules and package-lock.json are generated by the NPM.

React js Project structure
Figure 1 : React js Project structure

Using API

For simplicity, I utilize the publically available free API for testing and prototyping through jsonplaceholder.

How react app routing works

In react, the <BrowserRouter> uses the HTML5 history API to keep your UI in sync with the URL. I have implemented the routing in the App.js file. All the possible route changes are mapped to a component. As you convert this project into Next js, you will notice that routing in Next js is significantly different from react js.

React app HTML page source

Let's take a peek at how our react page Html looks like by viewing the page source. Start the server if you haven't done it yet.

npm start

Type http://localhost:3000, and you will see a table with randomly populated data. I am referring to the pages http://localhost:3000 and http://localhost:3000/posts.

React js app users
Figure 2 : React js app Users page
React js app posts
Figure 3 : React js app Posts page

Right-click on the page and click view page source. Interestingly, you will not find any content you see on the page in the HTML view.

How next js server-side rendering works

Unlike in React js, Next.js generates HTML on the server-side and sends it to the client. As I mentioned above, the server response is a pre-compiled Html document, similar to a common request and response-based web application.

The Next JS dynamic routing

The Next js uses a File-based routing system. Meaning, the URL follows the actual file path of the file. In Next.js, you can add brackets to a page to create a dynamic route. For example, [post].js file located in pages/post/ folder will create a dynamic route /post/1 or /post/post-title.

Next js pages directory

Pages directory is the source folder for all the components intended to be accessible via routes. When you add a file to the pages directory, the file is automatically available as a route. It is important to note that only the components inside the pages directory can be accessed through a route.

Converting react app to Next js

Converting a React js app to Next js is not that complicated. The convenience is you don't have to convert every single component in your React app. For example, you can convert the blog post page of your blog to Next js and continue using React js on the contact us page.

Create a Next js app with create-next-app

The easiest way to create a Next.js app is by using create-next-app. This simple command builds a new Next.js application, with everything set up for you. You can add any additional features as necessary by updating the package.json file. To get started, use the following command:

C:\learnbestcoding> npx create-next-app nextjs
C:\learnbestcoding> cd nextjs
C:\learnbestcoding\nextjs> npm run dev

Next js package.json file

Listed below is the package.json file created by create-next-app. Notably smaller and simple than React js package.json file.

{
  "name": "nextjs",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "10.0.9",
    "react": "17.0.1",
    "react-dom": "17.0.1"
  }
}

Using getServersideProps in next js

The getServersideProps() does the magic of rendering the component on the server-side. It is also important to note that unlike useEffect(), getServersideProps() is a server-side component and not visible to the browser. Therefore, javascript code does not work inside getServersideProps(). Note that changes are done on Next js components to add the getServersideProps() method. The API calls are done within the getServersideProps() and props are passed to the Next component as parameters.

Adding style sheets

Use pages/_app.js to import a global stylesheet to your application. Note that styles imported here are applied throughout the entire application. You also can create component scope style sheets with [name].module.css naming convention. Listed below is the globals.css style sheet located in the styles folder.

.topnav {
  background-color: #333;
  overflow: hidden;
}
.topnav a {
  float: left;
  color: #f2f2f2;
  text-align: center;
  padding: 14px 16px;
  text-decoration: none;
  font-size: 17px;
}
.topnav a:hover {
  background-color: #4CAF50;
  color: black;
}
.topnav a.active {
  background-color: #4CAF50;
  color: white;
}
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}
td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}
tr:nth-child(even) {
  background-color: #dddddd;
}

_app.js

Next js initialize pages by using the App component. Overriding the App component allows you to customize your pages. The overridden _app.js allows customizations such as layout management, state management, error handling, data injection, and adding global CSS.

import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}
export default MyApp

index.js (the equivalent of Users.js in the React js app)

Note that this index.js is not the equivalent of the index.js in the React js app. The Next js's index.js handles root requests, like a traditional web application. As I mentioned before, Next js uses the directory structure as the route path. Listed below is the complete Next js code for the React js application. The getServersideProps() method does all the magic.

import TopMenu from './TopMenu'
export default function Home({ users }) {
  return (
    <>
      <TopMenu />
      <table>
        <tr>
          <th>Name</th>
          <th>User Name</th>
          <th>Email</th>
        </tr>
        {users.map((user, index) => (
          <tr>
            <td>{user.name}</td>
            <td>{user.username}</td>
            <td>{user.email}</td>
          </tr>
        ))}
      </table>
    </>
  )
}
export async function getServerSideProps({ params }) {
  const data = await fetch('https://jsonplaceholder.typicode.com/users');
  const users = await data.json();
  return { props: { users } }
}

TopMenu.js

Note that Next js uses Link from 'next/link', instead of from 'react-router-dom'.

import React from 'react';
import Link from 'next/link';
const TopMenu = () => {
    return (
        <div className="topnav">
            <Link href='/'><a>Home</a></Link>
            <Link href='/posts'><a>Posts</a></Link>
        </div>
    );
}
export default TopMenu;

Posts.js

import React from 'react';
import TopMenu from './TopMenu';
constant Posts = ({ posts }) => {
    return (
        <>
            <TopMenu />
            <table>
                <tr>
                    <th>User</th>
                    <th>Title</th>
                    <th>Description</th>
                </tr>
                {posts.map((user, index) => (
                    <tr>
                        <td>{user.userId}</td>
                        <td>{user.title}</td>
                        <td>{user.body}</td>
                    </tr>
                ))}
            </table>
        </>
    );
}
export async function getServerSideProps({ params }) {
    const data = await fetch('https://jsonplaceholder.typicode.com/posts');
    const posts = await data.json();
    return { props: { posts } }
}
export default Posts;

Next js project structure

Below is our Next js project structure illustrated in the Visual Studio Code. Note that the node_modules, .next, and package-lock.json are generated by the NPM.

React js Project structure
Figure 4 : React js Project structure

Running Next js app in the dev server

Now it's time to start the server. Use the below command to start the server in development mode.

npm run dev

To make a production build, use:

npm run build

And run in production mode:

npm run start

Next js app HTML page source

If you take a look at the generated Html source by right-clicking on the page and selecting view page source, you will notice all the Html is available in the source code. This crawlable Html code helps search engines to understand and rank your content in an efficient manner.

Next js app users
Figure 5 : Next js app Users page
Next js app posts
Figure 6 : Next js app Posts page

When to use Next js over React js?

Usually, Next js is widely used in websites open to the public and SEO matters. Next js offers Reactjs-like capabilities including seamless and efficient page transitions with built-in SEO capabilities.

Conclusion

Converting a React js app to Next js can help enhance the page SEO score and page load speed. Next js speeds up the first-page load by compiling the Html response in the server.

Lance
By: Lance
Lance is a software engineer with over 15 years of experience in full-stack software development.
Read more...

Leave a comment

No Comments