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.
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.
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.
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.
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.
To make a production build, use:
And run in production mode:
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.
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.
Download React js simple api call
Download Next js converted from React js