Skip to main content
Next.js vs React - A Beginner's Guide
23 min read

Next.js vs React - A Beginner's Guide

This article was last updated on February 2, 2024 to organize content and add more use cases for better understanding of Next.js vs React comparison.

Introduction

This post provides an insightful comparison between Next.js and React. It briefly runs through their core innovation driven differences that should be considered while building React based applications.

Newbies often think there's some tech rivalry when they hear about React and Next.js. But in reality, React and Next.js share a parent-child bond. Just like in families where an achievement focused child performs to outshine her parents thanks to the strong support she gets from them, Next.js is grounded on React's foundations and brings its own strengths and innovations to the React ecosystem. It's so less about one being better or worse than the other.

The more useful, real questions would be: which one is simpler to learn for a new developer? Which feels safer? Do I have alternatives, or do I need to be a specialty focused/niche developer to use them? Understanding the relationships addressed by these questions helps make informed decisions about which technology to choose for your next project.

In this post, we provide an in-depth account of how Next.js compares to React - especially as a fullstack framework founded on React's component based fundamentals, and extended with demand driven patterns and conventions such as server side rendering (SSR), static / dynamic page generation, page based routing and backend API features. The primary goal of this comparison is to understand the core differences relevant of Next.js and React. This involves relating each other in terms of their architecture, conventions, configurations, and use cases they are best suited for. By understanding such fundamental relations, developers can gain enough insight into how each can be leveraged for different projects.

Steps we'll cover:

What is React?

React, also called React.js, is an open-source JavaScript library focused on building user interfaces, particularly for single-page applications. It equips developers to author super fast front-end applications by including HTML markup alongside rendering logic inside JavaScript based functions called components. Its core strength lies in a component-based architecture, that allows developers to encapsulate application concerns inside reusable components that determine how HTML elements and data in a page should be displayed on the screen.

React's major underlying technology is the Virtual DOM, which intelligently updates only the parts of a page that change, ensuring efficient and speedy rendering. React's Virtual DOM led to the idea of the client side rendering (CSR) pattern that facilitates building blazing fast single page applications (SPAs). React employs JSX (JavaScript XML) for templating, something that blends the familiarity of HTML with the power of JavaScript to first track and update pages in the Virtual DOM and then re-inject only the updated elements to the browser's document. This unidirectional data flow makes application markup, styling and complex business logic easily implementable only with JavaScript and helps developers build versatile frontend applications conveniently. This also makes React SPAs flexible and manageable on their own and gives frontend apps better integration capability with various backend technologies.

What is Next.js?

Next.js has evolved from the patterns, innovations and conventions that emerged around React -- especially with regards to single page applications (SPAs) and client side rendering (CSR) patterns. One of the landmark additions in Next.js has been file based routing, that addresses and conventionalizes resource based page routing and navigation necessary on top of a React SPA. Next.js also abstracts away and simplifies data fetching and caching via a set of functions that enable React based components and pages to efficiently manage API data. Next.js complements these to React's component based architecture.

It also extends an application beyond React-specific principles, particularly with the server side rendering pattern. Server side rendering or SSR in Next.js provides necessary architecture to empower React SPAs with static site generation or SSG that helps generate static pages at build time and on demand. Recent versions of Next.js (versions > 13) implement SSR with React Server Components that was introduced in collaboration with the React team at Facebook. Server Components composed with Client Components now let generated static pages communicate with the data layer and allow them to be updated with latest data in the client side. Perhaps the most distinctive extension related to server side rendering in Next.js is its versatility in building backend APIs with full support for server side routing, middleware integration and connecting to a database with ORMs like Prisma. Next.js' backend capabilities make it a React based full stack framework.

Overall, everything that can be possibly done in React can be effectively carried out in Next.js too, with the added but opinionated and conventionalized full-stack features.

In the sections and subsections ahead, we take a closer look.

Next.js vs React.js: The Core Differences

We base our comparison on the technologies built around the rendering patterns React and Next.js implement. Rendering behavior offers the most important distinction between Next.js and its underlying React components based foundations. While React.js implements client side rendering (CSR) itself, Next.js uses it to offer browser based rendering integrated under a more robust architecture that implements SSR in its Rust based application's server.

In order to understand better, let's discuss how SSR and CSR differ and how performance matters to both.

Server Side Rendering vs Client Side Rendering

There are many talks about server side rendering and client side rendering. What exactly are these, and how do they make Next.js and React.js different?

What is Server-Side Rendering?

Server-side rendering (SSR) is a pattern in which a website's content is generated on the web server instead of the browser. In this approach, the server creates the entire HTML representation of the application in the beginning and sends it directly to the client or browser. This takes a few milliseconds with a good internet connection.

Since the emergence of the browser, the known method for getting your HTML onto a screen was by using server side rendering. When Facebook first launched in 2004, web development was primarily server-centric. Websites, including the initial version of Facebook, relied heavily on server side rendering. This meant that every time a user performed an action, like adding a friend or posting a status, the server generated a new HTML page and sent it back to the browser. This often resulted in full-page reloads.

As web development evolved, the concept of client side rendering (CSR) gained traction. Instead of relying on the server to render every change, CSR leverages the power of the user's browser to dynamically update the content.

What is Client Side Rendering?

Client side rendering, or CSR is the technique of dynamically generating content in the browser using JavaScript. With CSR, instead of receiving a fully populated HTML document from the server, the browser obtains a skeletal HTML structure accompanied by a JavaScript file. The JavaScript code in this file then takes on the responsibility of rendering the remainder of the site's content directly within the browser. With CSR, the user would experience a little delay as the browser needs to load the Javascript first.

It's important to note that client side rendering is a relatively new pattern of displaying site content. In particular, CSR has been popularized by the emergence of React.js as it was incorporated as an important approach into their development.

Advantages of Client Side Rendering

Imagine the early Facebook feed. With SSR, every time you liked a post or added a comment, the entire page needed to refresh to reflect that change. With CSR, only the specific section of the page that changed (e.g. the like count or the comments section) would update without a full page reload. This provides a smoother and faster user experience.

CSR or SSR: Which Pattern is Better?

Deciding which rendering pattern is better depends on what exactly needs to be implemented. However, the difference between CSR and SSR is not a factor in deciding the difference rendering options make between Next.js and React. This is because Next.js implements React's client side rendering out of the box. And on top of React's CSR, Next.js implements server side rendering as well.

And that's not all. Next.js' versatility enables it to perform React's client side rendering in two ways.

First example, using the useEffect() hook:

import React, { useState, useEffect } from "react";

export function Page() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);

useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (e) {
setError(e);
console.error("An error occurred while fetching the data: ", e);
}
};

fetchData();
}, []);

if (error) {
return <p>Error fetching data: {error.message}</p>;
}

return <p>{data ? `Your data: ${data}` : "Loading..."}</p>;
}

This approach belongs to all React applications, as you could choose to use the useEffect() hook for any good use case in React. However, in case of data fetching, it's advisable to use a specialized library like React Query for better app performance and development efficiency.

Here's a second way of fetching data on a Next.js based React page with a minimalistic example using React Query:

import React from "react";
import { useQuery } from "react-query";

// Function to fetch data
const fetchData = async () => {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
};

function App() {
// Using the useQuery hook to fetch data
const { data, error, isLoading } = useQuery("fetchData", fetchData);

if (isLoading) {
return <p>Loading...</p>;
}

if (error) {
return <p>Error: {error.message}</p>;
}

return (
<div>
<h1>Data from API:</h1>
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}

export default App;

Next.js' multiple options clearly offer advantages to the developer. In other words, unlike it is in React.js, Next.js equips a developer with alternative component rendering choices.

Next.js implements server side rendering with the help of an async function called getServerSideProps. This function is called by the server for each incoming request.

For instance, if your page requires pre-rendering of data that updates regularly (sourced from an external API), you can write a getServerSideProps function. This function fetches the necessary data and supplies it to the <Page /> component as illustrated below:

export default function Page({ fetchedData }) {
// Use fetchedData for rendering...
}

// Called on every server-side request
export async function getServerSideProps() {
// Retrieve data from the external API
const response = await fetch("https://.../data");

if (!response.ok) {
throw new Error("Failed to fetch data");
}

const fetchedData = await response.json();

// Supply data to the page through props
return { props: { fetchedData } };
}

Generally, it's important to note, in terms of initial page load times, that a server-rendered application using Next.js could potentially offer faster performance compared to that in a client-side rendered React application. The difference in performance can vary based on several factors, but the benefits of SSR -- especially for SEO and perceived load times -- are notable.

Static Site Generation

Static Site Generation (SSG) is closely associated to the server side rendering pattern. SSG is a much desired product of SSR that has gained popularity in search engine optimized pages development, as it offers faster initial page loads. Both React and Next.js provide mechanisms for SSG, but they differ in their implementation and capabilities.

React: The Foundation

React by default, renders content dynamically in the browser. To achieve SSG with React, developers would often rely on additional tools and configurations. React doesn't natively support SSG. Instead, developers use tools like Create React App in conjunction with static site generators to pre-render content.

Next.js: Enhanced SSG Capabilities

Next.js takes SSG to the next level by offering in-built support for static site generation and an easy developer experience. Pages in Next.js without any data-fetching methods are by default pre-rendered as static HTML, optimizing them for performance.

Next.js has the getStaticProps function, which allows developers to fetch data at build time and generate static pages. This is ideal for content that doesn't change frequently. In development mode, next dev, pages are compiled on demand. Whereas, the production build pre-renders pages to HTML and optimizes them for deployment.

The terminal provides clear logs showing which pages are being statically generated. With next export, developers can generate a fully static site suitable for hosting separately on static site platforms.

Data Fetching and Caching

Data fetching and caching in Next.js is also related to how they are implemented along side server side rendering in Next.js. Let's look at how they would compare to approaches in plain React.

React: Data Fetching and Caching with External Libraries

React needs external libraries for fetching backend API data, caching them and managing their states. Plain React developers have to come up with their own opinions, components, architecture and configurations for using libraries like XState, MobX or Redux for client data management, and React Query, Redux Toolkit Query or SWR for managing server data.

Data Fetching in Next.js

Next.js, in contrast, offers battle tested and well-adopted conventions to fetch server side data using a set of functions suited for generating static and dynamic pages in the server side as well as for rendering dynamic components in the client.

For example, getStaticProps is used to fetch data from an external API and populate a static page that is rendered server side at build time. getServerSideProps is the server side dynamic equivalent of getStaticProps. In other words, getServerSideProps is used to re-fetch API data for rendering a page server side at every request. This happens in conjunction with the file based page routing that Next.js implements as part of server side rendering.

In version 13, Next.js introduced the App Router based routing and rendering mechanism which comes with out-of-the-box server data management. It offers server side data fetching, caching and revalidation by extending the fetch Web API. Next.js data fetching and caching can be used for server actions and mutations inside React Server Components with the use server directive for rendering pages statically and dynamically. The App Router based conventions implement client side rendering with Client Components which can be composed with Server Components with the use client directive.

Routing

Next.js' server side rendering pattern complements its intuitive page based routing conventions. While React itself doesn't provide built-in routing solution for CSR, it is open to integration with any library of developer's taste. In this section, let's understand how routing in React and Next.js compares.

React Routing with Library of Your Choice

React is unopinionated about routing. This almost means, we have to rely on any routing library we choose to facilitate navigation inside a React SPA. A top downside is we may have to build our routing system ground up if we don't use an intuitive library that gives us conventions necessary of a RESTful API. A top advantage is that we can choose a library of our choice to build our own conventions.

The React Router Library

The most popular choice for implementing routing from scratch in React applications is the React Router library. It provides a set of components and hooks to define and manage routes.

For example, routes are declared using the <Route /> component. And navigation is typically handled by the <Link /> or <NavLink /> components.

import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";

function App() {
return (
<Router>
<>
<Link to="/our-team">Our Team</Link>
<Routes>
<Route path="/our-team" element={<OurTeam />} />
</Routes>
</>
</Router>
);
}

function OurTeam() {
return <div>Welcome to the Our Team page!</div>;
}

Dynamic Routing in React

With React Router, dynamic routes are achieved using the :parameter syntax. For instance, to create a dynamic route for team members, you'd use a path like /team/:memberId.

import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";

function App() {
return (
<Router>
<>
<Link to="/team/jane-doe">Jane Doe's Profile</Link>
<Routes>
<Route path="/team/:memberId" element={<TeamMember />} />
</Routes>
</>
</Router>
);
}

function TeamMember(props) {
return <div>Welcome to the profile of {props.match.params.memberId}!</div>;
}

Next.js: File Based Opinionated Routing

Next.js, in comparison, brings intuitive simplicity with page based routing conventions, as complemented to its SSR pattern. Server side routing with file based conventions makes page development super efficient. Let's see how:

Next.js Basic Page Routes

In Next.js, the src/pages directory isn't just a typical folder. It forms the heart of your application's server side routing. The framework eventually transforms the file structure in this directory into page routes. This is what the directory looks like:

    pages
|-- OurTeam.tsx -> /our-team
pages/our-team.tsx
function OurTeam() {
return <div>Welcome to the Our Team page!</div>;
}

export default OurTeam;

Page based routing offers the convenience plain React developers often seek while building RESTful pages. And again, Next.js provides this out-of-the-box.

Dynamic Routes in Next.js

For scenarios where static paths aren't needed, Next.js understands that not all routes are static. It uses sensible defaults to infer routes for dynamic pages from the folder structure and the page identifier passed as a prop.

For example, if you're building an e-commerce site and want to display members details dynamically, you can use a structure like below:

    pages
|-- team
|-- [memberId].tsx -> /team/jane-doe, /team/john-doe, etc.

This is what the code would look like:

pages/team/[memberId].tsx
function TeamMember({ memberId }) {
return <div>Welcome to the profile of {memberId}!</div>;
}

// Fetching the memberId from the route
export async function getServerSideProps(context) {
return {
props: { memberId: context.params.memberId },
};
}

export default TeamMember;

Next.js Routes Navigation: the <Link /> Component

Next.js provides a custom <Link /> component. This component pre-fetches data when a user hovers over a link, enhancing the application's performance.

import Link from "next/link";

function HomePage() {
return (
<div>
Welcome to the Home page!
<Link href="/our-team">Visit Our Team</Link>
</div>
);
}

export default HomePage;

Again, this is more opinionated than plain React Router <Link /> components.

Conventions like file based routing have potential lock-in drawbacks but Next.js can lead our way long before we decide on some custom configuration, opt for other conventions or think of stepping back and start building ground up with pure React. But Next.js gives mostly what an average React developer would want to already have if they are given a choice to build routing from scratch.

In Short:

  • Simplicity vs. Flexibility: Next.js offers a straightforward, file-based routing system, making it easy for developers to set up routes without additional configuration. React, provides more flexibility but requires manual setup of a lirbary of choice.
  • Performance: Next.js' <Link /> component enhances performance by pre-fetching pages, a feature not inherent in React routing.
  • Learning Curve: For beginners, Next.js's intuitively opinionated routing can be easier to grasp. Meanwhile, React's routing, while powerful, might have a steeper learning curve due to its nature.

React vs Next.js: Backend API Development

Apart from the above considered core functionalities, we can compare React with Next.js in terms support for backend API development.

While React backend API development would be a silly noise without Node.js based backend frameworks like Express.js or Nest.js, Next.js is equipped with the necessary Rust-based architecture for implementing API routes. Next.js API routes enable development of backend APIs within a React based (!) framework. API routes in Next.js are available in both the Page Router and App Router conventions.

In the Page Router system, API routes are built using the NextApiRequest and NextApiResponse APIs that come with respective helpers for handling requests and responses.

In the App Router convention available in versions 13 onwards, backend APIs can be developed using the Next.js Router Handlers or Server Components APIs.

Next.js vs React: Configuration Differences

In this section, we compare the configuration differences between React and Next.js.

React apps created with Create React App require manual handling of configurations that demand extensive depth of understanding in using Babel and Webpack, along with other tools. While Next.js differs with a minimal configuration philosophy.

Let's take a closer look.

React Configuration

The most common way to set up a new React project is using Create React App. It provides a setup with Webpack, Babel, and a development server. However, it abstracts away the configuration, making it a bit complex to customize without "ejecting" (which exposes all configurations).

While customizing the configuration of a CRA project, you need to "eject" it. This process generates all the configuration files, giving us full control. However, once ejected, we can't go back, and we have to manage updates and configurations manually.

React doesn't enforce a specific folder structure. While this offers flexibility, we need to decide on a consistent structure and architecture and stick to it for stability. React also doesn't have a default way to handle CSS. We can use regular CSS, and CSS modules, or opt for CSS-in-JS solutions like styled-components. Each method requires in-depth configuration and dependencies.

Next.js Configuration

One of the standout features of Next.js is its "zero-config" approach. While Next.js works great with no configuration, there are times when we might need to customize its behavior with the configurations of our choice. This is where the next.config.js file comes into play because the default minimal configurations came prepared in this file. The Next.js config file, placed at the root of your project, allows us to tweak various aspects of Next.js, from setting up environment variables to customizing Webpack and Babel configurations.

Next.js also has built-in support for TypeScript. Simply adding a tsconfig.json file to our project is enough for Next.js to configure TypeScript for us. At its best, Next.js supports global CSS, module CSS, and Sass out of the box. We can also easily integrate other CSS-in-JS libraries using plugins or custom configurations.

React vs Next.js: Use Cases and Suitability

After the above in-depth comparisons at different levels, we can agree that Next.js uses React's component based architecture and client side rendering capabilities to its absolute advantage in implementing full stack features with server side routing and component rendering to generate pages statically and dynamically both in the server side and in the client side.

This just makes React's minimalism and flexibility instrumental inside frameworks like Next.js and Next.js' innovation driven patterns versatile in developing high performance React based applications across industries.

When to Use Next.js

Next.js is an ideal choice for a range of applications, particularly for e-commerce sites, blogs and other page based sites such as social media or niche networks.

Next.js' strength lies in its hybrid rendering capabilities, offering server-side rendering (SSR), client side rendering (CSR), dynamic rendering and static site generation (SSG). Combining these patterns in a Next.js app effectively ensures fast page loads, which are very important for e-commerce sites and blogs where user experience and immediate visibility directly impact success.

When to Use React

On the other hand, React.js in its core form, provides a highly configurable and flexible foundation. It's particularly well-suited for building small scale, simple, dynamic single-page applications (SPAs) such as personal pages, landing pages, portfolios or anything that requires frequent data updates but minimal authentication and routing capabilities. These applications benefit from React's component-based architecture, enabling seamless user interactions without frequent page reloads.

React is also good for building fully capable robust frameworks ground up like Next.js itself, Refine or Remix. So, given enough resources, time and problems to address with React's component based system, you'd definitely like to start ground up with a minimalism and build our own set of opinions and conventions around React.

React and Next.js: Starting Up Both Projects

To start up a development area for Next.js and React follow the procedures outlined in the following sections.

Starting a React App

Installation

First, ensure you have Node.js and npm (Node Package Manager) installed. If not, download and install them from Node.js official website.

How to Initialize a React App

You can create a new React app using Vite. Follow the below steps:

  1. Open a terminal or command line tool and run the following command:
npm create vite@latest

This opens up the Vite shell that asks for a project name. Enter your project's name and press the enter key. Following that, Vite will ask you to choose a framework. Opt for React.

  1. Navigate to your app
cd react-app-name
  1. Run the following command to start the local development server:
npm install && npm start

Your React app will be running in the port listening at http://localhost:3000/.

Initializing a Next.js App

Installation

Just as with React, ensure you have Node.js and npm installed. Make sure no server is running at port 3000 and then follow the steps below:

  1. Create a new Next.js app. Use the npx (online) CRA CLI command to initialize a Next.js project:
npx create-next-app next-app-name
  1. Navigate to your app:
cd next-app-name
  1. Start the development server by running the following command:
npm run dev

Your Next.js app will now be running on http://localhost:3000/.

If you haven't stopped the React server running on port 3000, you might want to change the port for this one to avoid conflicts.

Summary

In this post, we explored the difference between React and Next.js by understanding the differences in which they implement rendering patterns, partciularly the way Next.js relies on React's client side rendering capabilities and extends them with server side rendering and associated architecture that is often sought in React SPAs.

We acknowledge that Next.js being a more robust framework that enables full stack development with out-of-the-box server side routing, static site generation and data fetching functionalities, provides scalable solutions for use cases like a large scale e-commerce platform, a blog site with millions of static pages or a page based listing site. In contrast, pure React is convenient for simple SPAs like a landing page, portfolio or a personal page that involves a frequent user actions and data updates.

Related Articles

A Guide to useContext and React Context API

Share data across components with React Context in TypeScript and Next.js

React useMemo Hook Guide with Examples

Improve React app performance with the useMemo() hook.

Error Boundaries in React - Handling Errors Gracefully

We'll see how to handle errors in React using Error Boundaries and how to use the react-error-boundary library to handle errors in React.