Software Engineering

Next.js vs. React: A Comparative Tutorial


Next.js is a lightning-fast React framework trusted by data-heavy streaming sites like Hulu and Netflix. If you’re already versed in React, you should definitely get to know this increasingly popular technology.

Though both React and Next.js help create effective web user interfaces, they have some key differences: Next.js is more feature-rich and opinionated than React. It is especially well-suited for websites focused on search engine optimization (SEO) or pre-rendering.

Next.js vs. React

React, which debuted in 2013, is much more established than Next.js. But the younger framework, released in 2016, is growing in popularity, with more than 100K GitHub stars as of March 2023 and millions of weekly npm downloads. Let’s see how the two compare in four major areas:

  • Development speed: Next.js provides out-of-the-box features that ease the development process for making an advanced React app. With the introduction of its own compiler in Next.js 12, the framework also increased build speeds. Compared to React, Next.js reduces the amount of time an engineer needs to wait for code to refresh, minimizing developer frustration and slowdowns.
  • Data fetching and load times: Next.js can traverse the React tree and query for data in the server, allowing for pre-loaded page data. This often results in lower application load times for pages served by Next.js compared to pages written in vanilla React.
  • Rendering and SEO: Next.js offers pre-rendering, whereas React uses client-side rendering. Pre-rendered pages enable effective SEO strategies that are challenging to achieve in a plain React app.
  • Routing: Next.js provides a structured, predefined file system for routing. Its system offers reduced flexibility compared to React’s various library options (e.g., React Router), but simplifies page setup and routing.

React serves a variety of project types very well, including user dashboards, back-end systems, internal organization tools, and data visualization systems. Next.js is the ideal toolkit with which to enhance React applications that benefit from the power of pre-rendering, including e-commerce stores, social media apps, ticket-booking systems, and education platforms. Let’s explore some of its use cases in more detail.

Rendering in Next.js

Rendering is the process that converts React code into HTML that the browser then displays as the page’s user interface. Next.js provides three rendering methods—client-side rendering (CSR), server-side rendering (SSR), and static site generation (SSG)—and the added bonus of incremental static regeneration (ISR). ISR combines server-side rendering with a semi-static caching mechanism that relieves server load and provides speeds similar to those achieved by a static site.

Server-side rendering and static site generation fall under the umbrella of pre-rendering, in which HTML pages are generated before being sent to the client side. A great advantage of using Next.js is that it adds powerful support for pre-rendering React apps.

Client-side Rendering

Client-side rendering is the default for vanilla React applications. This method generates the page’s HTML on the client side. In other words, rendering efforts take place in the user’s browser, and JavaScript from the user’s device generates the HTML. The UI appears after the rendering is complete, when the webpage is also interactive (i.e., hydrated).

CSR is possible for Next.js components using React’s useEffect or useSWR.

Server-side Rendering

Next.js also enables the generation of a page’s HTML on the server. In this case, the generated HTML is sent to the client so that the webpage’s UI appears before hydration. Then, the viewable webpage is ready for interaction after the client finishes initializing the JavaScript.

On pages where we want Next.js to perform server-side rendering, some simple configuration functions are added to the page.

Static Site Generation

Next.js also offers static site generation, in which all static HTML pages are rendered from the JavaScript at build time. Generating a static site from a React code base requires more upfront build time compared to a React single-page application. However, the payoff here is having static content that can be served and cached at the maximum speed allowed by the site content without the computational overhead of SSR.

We can perform SSG on Next.js pages that we want to generate statically with getStaticProps() and getStaticPaths(), the latter of which defines the routes for static pages.

Next.js Search Engine Optimization

Next.js’s speed and ability to pre-render all pages of a website allows search engines to quickly and easily crawl and index the website, improving SEO. SEO is critical for many businesses and websites because websites with better SEO appear higher in search results. Users are more likely to click on higher-ranked websites, with the top result having an average click-through rate of 27.6%, a rate that is ten times greater than the tenth result’s click-through rate of 2.4%.

React websites with large amounts of content—and the resultant JavaScript code used for rendering—face SEO challenges when dealing with Google crawling and indexing.

The ability of Next.js to easily perform server-side rendering (SSR) not only enhances SEO rankings, but also improves a website’s perceived and actual load time for an optimal user experience.

Getting Started With Next.js

Now we’ll look at the essentials of Next.js setup, routing, pages, and navigation so you can reap the benefits of pre-rendering and SEO. Before starting our Next.js tutorial, ensure that you have the latest version of Node.js downloaded on your system. You can verify the Node.js version on your machine with node --version.

There are two ways to set up a Next.js project:

  • Automatic setup, with predefined configurations
  • Manual setup and configurations

We’ll follow the automatic setup to get started on our project more easily. The create-next-app CLI tool manages the automatic setup and makes it possible to quickly build applications. Let’s create our project with the built-in Next.js support for TypeScript, a strict syntactical superset of JavaScript, to ensure proper type structure:

npx create-next-app@latest --typescript

create-next-app will ask for the name of your application. Your project name must consist of lowercase letters and use hyphens instead of spaces. For example, I’ve named my application next-js-tutorial. Once setup is complete, you’ll see a success note in your terminal.

In our new project, we can see that Next.js mandates a rigid file structure system:

  • The website’s pages and styles are organized into their own folders.
  • APIs are stored in the pages/api folder.
  • Publicly available assets are held in the public folder.
The structure of the “next-js-tutorial” folder contains four folders: node_modules, pages (containing an “api” subfolder), public, and styles.
File Structure for Next.js Projects

Next.js Routing and Pages

Next.js uses its file structure inside the pages directory to define the application routes.

We define all routes in the pages folder. The default pages/index.tsx file is the application’s entry point where we define custom fonts, application tracking codes, and other items requiring global access.

There are two methods for adding new routes:

  • Add a file ending in .tsx directly in pages.
  • Add an index.tsx file under a new subfolder of pages (index files are automatically routed to the directory root).

Let’s examine a few concrete Next.js examples for routing. We’ll implement a simple page route for our tutorial, then touch on nested and dynamic routing concepts.

Page Routes

We can add a basic page route, such as about-us, with an about-us.tsx file:

|— pages
|    |— _app.tsx
|    |— about-us.tsx
|    |— api
|    |— index.tsx

Or we can use an index.tsx file under a pages/about-us folder:

|— pages
|    |— _app.tsx
|    |— about-us
|    |    |— index.tsx
|    |— api
|    |— index.tsx

Go ahead and add the about-us.tsx page route to your project.

import styles from '../styles/Home.module.css'

const AboutUs: NextPage = () => {
  return ( 
    <div className={styles.container}>
      <main className={styles.main}>
        <h1 className={styles.title}>
          About Us Example Page
        </h1>
      </main>
    </div>
  )
}

export default AboutUs

We’ll see page routing in action when we use it in combination with Next.js navigation. For now, we’ll return a placeholder NextPage with a title string so the navigation will work properly.

Nested Routes

Nested routes allow for multiple layouts to be reused and selectively updated on a page (for example, when a user clicks a URL, you may want to update the body content but preserve the header and footer layouts).

|— pages
|    |— _app.tsx
|    |— api
|    |— index.tsx
|    |— parent
|    |    |— child.tsx

We define the nested route /parent/child with a parent folder and a nested child folder or file (our example shows a file).

Dynamic Routes

Dynamic routes allow layouts to respond to real-time changes in cases where predefined paths do not suffice. Let’s say we want to create a /product/[productId] route (i.e., when a product is clicked, expand its component). Assuming that productId is a variable accessible in our definition of Product, we can easily add a dynamic route by creating a product folder and wrapping our productId page in brackets:

|— pages
|    |— _app.tsx
|    |— api
|    |— index.tsx
|    |— product
|    |    |— [productId].tsx

This way, a route like product/testId will have its query parameters set (i.e., productId is set to testId).

Finally, it is also possible to combine routing techniques. For example, we could create the nested dynamic route pages/post/[postId]/[comment].tsx.

Next.js Navigation

Next.js uses its own custom Link components instead of the <a> HTML tag when navigating between client-side pages to allow Next.js to optimize navigation and data pre-fetching. Link operates similarly to the <a> tag and uses href as the route to be opened. You must use the passHref prop to force Link to pass its route value to child components (i.e., when using custom-styled components).

The major benefit to using Link instead of the <a> tag is that it pre-loads data in the background when a user hovers over or near a link. This makes the content more readily available for the client to process, delivering improved app performance. You may still use the <a> tag in Next.js when linking to external pages outside of the app.

To link to our About Us page from the Next.js project blueprint, open the pages/index.tsx main app file. We’ll first import the Link component from next/link, and then add a linked paragraph below the <h1> component:

<p className={styles.description}>
  Here's our example <Link href="/about-us">About Us</Link> page
</p>

Now we can run our app using the npm run dev shell command, visit http://localhost:3000, and see our added text and About Us page working at http://localhost:3000/about-us (the route returned after clicking About Us).

The Next.js website template displayed at “localhost:3000,” with an added description below “Welcome to Next.js!”: “Here’s our example About Us page.”
A website at the address “localhost:3000/about-us” displays “About Us Example Page.”

Supercharge Web Apps With Next.js

There are many factors to consider before choosing a framework for your next website. Though Next.js is more opinionated and less flexible than React, the framework offers great out-of-the-box functionality for advanced projects targeting SEO or pre-rendering capabilities. With the foundations of Next.js in your toolkit, you can supercharge your site and get an edge over vanilla React applications.

The editorial team of the Toptal Engineering Blog extends its gratitude to Richard Plotkin for reviewing the code samples and other technical content presented in this article.