How to convert your React projects to Next.js, problems you may encounter, plus some tips and tricks.

Jan 15, 2024 - 14 min read

react-to-nextjs

(Next.js has exploded in popularity in recent years, this article explains why converting your React apps to Next.js might make sense, lists key steps for creating a new Next.js project and migrating your code, and highlights some additional issues to consider along the way.)

Jump to Section:

What is Next.js?

First, what exactly is Next.js? From the official documentation... "Next.js is a React framework for building full-stack web applications. You use React Components to build user interfaces, and Next.js for additional features and optimizations. Under the hood, Next.js also abstracts and automatically configures tooling needed for React, like bundling, compiling, and more."

So right off the bat we see that Next.js offers full-stack capabilities as opposed to React which is intended for front-end (e.g. browser) and SPA development. Created by Vercel, Next.js is an open-source JavaScript framework that allows fast development of user-friendly web applications and static websites, while also offering an out-of-the-box solution for server-side rendering (SSR) of React components.

In other words any application you have that uses separate code bases for the front-end and back-end (e.g. React + Node/Express) can be replaced by Next.js - simplifying your application into one single project and streamlining communication across your stack.

React vs Next.js

One key difference between React and Next.js already mentioned is React only offers client-side rendering, whereas Next.js allows for client-side, server-side, and static site generation on a per component basis. This level of flexibility makes development of full-stack applications highly customizable and offers numerous potential gains based on your app setup and needs.

Next.js offers great image Image optimization and SEO functionality out of the box as well as automatic code-splitting. Next.js is also highly customizable in project configuration and setup where React is much less configurable - unless you eject the project if using create-react-app which in turns removes some of the advantages CRA provides in the first place.

Other key differences include:

1. Server-Side Rendering (SSR) and Static Site Generation (SSG)
ReactInitially designed for client-side rendering, though server-side rendering can be implemented using 3rd party tools and libraries.
Next.jsSupports server-side rendering and static site generation out of the box, improving performance and SEO.
2. Routing
ReactRequires additional routing libraries (e.g., React Router) for client-side routing.
Next.jsHas built-in routing, making it simpler to handle navigation between pages.
3. File System-Based Routing
ReactStructure and organization are left to developers, and routing needs to be configured explicitly.
Next.jsFollows a convention-based approach for routing based on the file system, making it intuitive and reducing the need for manual configuration.
4. API Routes
ReactDevelopers need to set up a separate server for handling API requests.
Next.jsProvides built-in API routes, making it easy to create serverless functions and handle backend logic within the same codebase.
5. Development Environment
ReactDevelopers need to configure their own development environment and tools.
Next.jsComes with a pre-configured development environment, reducing setup time.
6. Data Fetching
ReactDevelopers need to handle data fetching in various ways, such as using fetch or external libraries.
Next.jsSupports both server-side and client-side data fetching methods, providing flexibility based on the use case.
7. Community and Documentation
ReactSince React has been around longer it has a bigger developer community and solid documentation.
Next.jsWhile the documentation for Next.js is decent, it has not been around as long as React and therefore has a smaller developer community and less documentation in general.

In summary, while React is a library focused on building UI components, Next.js is a framework that extends React's capabilities by adding features like server-side rendering, routing, API handling, and other benefits to streamline the development process.

Migrating Your Code

Ready to convert your pure React project to Next.js? Wondering where to start? Similar to CRA for React projects, you can use create-next-app to quickly create and configure a new Next.js project from scratch.

Create a Next.js Project

To create a new Next.js project open a terminal and run the following command:

% npx create-next-app@latest

Running the above command will start an interactive prompt similar to the following:

% npx create-next-app@latest
✔ What is your project named? … my-app
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes

Make your selections based on your project configuration and needs. For example my projects are currently pure Javascript and I'm using Bulma for CSS so I selected No for Typescript and Tailwind options. I personally also usually opt to not create a separate /src directory either to simplify the project structure and remove redundant folder levels.

After initial setup your project should look similar to the following:

├── app
│   ├── favicon.ico
│   ├── globals.css
│   ├── layout.js
│   ├── page.js
│   ├── page.module.css
├── public
│   ├── next.svg
│   └── vercel.svg
├── jsconfig.json
├── next.config.js
├── package.json
├── README.md
├── .gitignore
└── .eslintrc.json

Now you can install project dependencies:

% npm install

And run the project in local development mode:

% npm run dev

You can view the running app by going to http://localhost:3000 in a web browser, you should see the following:

react-to-nextjs

Copy Source Files

Now you are ready to copy your existing source files into your new project. Since Next.js supports React components you can copy-paste your existing components into the /app folder in your new project with minimal or sometimes no changes needed. Or you can copy your source files into a different level project directory, however note the following:

Next.js components are configured to render server-side by default so for any React components that need to be rendered client-side (for example they use browser APIs such as fetch or local storage) you will need to add the 'use client' directive to the top of said source file. More Info

For instance, if you have the following generic component that uses the browser fetch API:

import React, {useState, useEffect} from 'react'

export default function MyComponent() {

  const [data, setData] = useState("")
  
  useEffect(() => {
    async function getData() {
      await fetch("https://some.domain")
      .then(response => response.json())
      .then(data => setData(data.message))
    }
    getData()
  },[])

  ...
}

You would need to add the 'use client' directive to the top of the source file for it to function properly:

'use client'

import React, {useState, useEffect} from 'react'
...

Update Routing

Unless you're hosting a React SPA with a single home route you will likely need to update your components to allow for front-end routes to work properly. Import the Link component from next/link to create links between pages. If your components need to access the router object then you can use the useRouter hook.

As is common with other code ports, you may also need to update some of your import ... statements across your components as references to resources like images and CSS files may have changed under the new project structure.

Data Fetching

Data fetching is handled a little different in Next.js. Instead of using React Router to define your routes and fetch data using a library like axios or fetch, Next.js routing is handled based on the file structure in the /pages directory, and data fetching is done using the getServerSideProps or getStaticProps functions.

The getServerSideProps function is used to fetch data at request time, hence it is called every time the page is visited. On the other hand, getStaticProps is used to fetch data at build time, which means it will be called once when the app is built and the data will be included in the generated HTML for the page (e.g. sites that use static generation).

You’ll need to export one or more of these functions from a page component in order to use it, for example:

import axios from 'axios';
export async function getServerSideProps() {
  const res = await axios.get('https://my-api.com/data');
  const data = res.data;
  return {
    props: {
      data,
    },
  };
}
export default function Page({ data }) {
  // render data in the component
}

For more info including additional functions and options see the data fetching page.

Add Scripts (optional)

Next.js supports global scripts and metadata imports as well on a per layout basis (e.g. for a subset of routes). For simplicity we show how to add a global script to your Next.js project below.

Let's say you had the following script defined in your previous project's index.html file:

<head>
  <script src="https://www.googletagmanager.com/gtag/js?id=G-123456789" data-nscript="lazyOnload"></script>
</head>

In your Next.js project open /app/layout.js and add the following import at the top:

import Script from 'next/script'

Now we can use the <Script> component to import the global script in our Next.js app:

import Script from 'next/script'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
      <Script 
        src="https://https://www.googletagmanager.com/gtag/js?id=G-123456789" strategy="lazyOnload"
      />
    </html>
  )
}

(For more information on importing and optimizing scripts see the Next.js scripts page.)

By now hopefully you've ironed out any remaining errors and you're able to run your new Next.js app. If you still have some compilation or run-time issues the Next.js Docs are great place to start.

Tips and Tricks

Images

Next.js offers strong image capabilities and optimizations through the Image component. If you have a lot of images in your app it's a good idea to go through and replace them with the Image component so you can take advantage of optimizations and other benefits. Here's its basic usage:

import Image from 'next/image'

export default function Page() {
  return (
    <Image
      src="/profile.webp"
      width={500}
      height={500}
      alt="Picture of the author"
    />
  )
}

Note on Hero Images

If you have a hero image on any of your pages there's some extra properties you will need to set along with some styling to make it work properly and take up the whole screen. After lots of reading and tinkering I finally got full-screen heros working with the following code:

export default function FullScreenHero(props) => {

  return (
    <div style={{position:'relative',width:'100vw',height:'100vh'}}>
      <Image
        priority
        src={`/path/to/hero.webp`}
        fill
        style={{objectFit:"cover"}}
        alt="full-screen-hero"
      />
    </div>
  )
}

The priority property tells Next.js this image accounts for the Largest Contentful Paint (LCP) on this page and to therefore prioritize loading of this image. Also note the extra fill and style properties as well as some extra CSS styling on the parent div which are required (e.g. parent div must have relative positioning).

Global CSS imports

If your previous React project imported global CSS/SCSS files across multiple source files then you may need to refactor some of those imports. In Next.js global style imports are only allowed under the /app source folder, meaning you may need to move source files around or update your styling imports.

In addition to global styling Next.js also supports CSS Modules, Tailwind CSS, Sass, and CSS-in-JS, see the styling page for more info.

Production Builds

To create an optimized production build use the next build command by running:

% npm run build

This creates a production ready build that can be self hosted on a Node.js server, Docker Image, or as static HTML files (see deployment). However you may notice no traditional build folder is generated by the command. To do so we must explicitly tell Next.js to create one. Open next.config.js and add output: 'export' to the nextConfig object like so:

const nextConfig = {
  output: 'export',
}

module.exports = nextConfig

Now when you run npm run build a new /out folder will be generated at the root of your project that contains your production build's static assets.

Hopefully this article has helped you convert your existing React project into a Next.js app and you are reaping some of the benefits the Next.js framework offers. Plus with Next.js you now have a foundation to start adding any backend / API routes to your app - all within the same code base.


Thanks for reading!

by Ergin Dervisoglu (Jan 15, 2024)