Next.js vs. React: Comparing the developer experience

When choosing any software library or framework, one normally considers the developer experience. When I say “developer experience” here, I mean to say what it’s like for developers to actually do the task. Developers generally favor libraries or frameworks that are fun and easy to use. This is a major reason why we have the leading libraries and frameworks today.

In the world of React, Next.js has become a popular framework for “hitting the ground running.” As a React fan myself, I picked up Next.js a few months ago and have really enjoyed working with it on my projects. I liked it so much, that I even rewrote my personal blog using Next.js (check out my post on it).

Next.js builds on top of React to provide a streamlined development experience. There is a small learning curve with Next.js, but even developers new to the world of frontend can get up and running relatively quickly. That being said, there is definitely a different experience when building a project with Next.js vs. React.

This post compares the developer experience of Next.js vs. React. I’ll walk through some background first and then dive into more specifics, discussing what it’s like to initially start a project, build pages, retrieve data, use the documentation, and perform advanced actions with both Next.js and React.

I’ll be referring to a sample project that you can find in my GitHub repo here. This project shows two different implementations of a fan site about the hit show The Mandalorian. The react folder in the project is the React version, of course, and the next.js folder contains the Next.js version. Both projects work and should only need the standard npm install to get up and going.

Some background

Before we dive into the actual developer experience, it helps to have some background.

A React Banner

React was originally created by Facebook and has become one of the most popular libraries in the frontend world today. React is easily extendable and can include features like routing as well as state management patterns with libraries like Redux. React is minimal in its footprint but can be customized for almost any project. For more about React on a high level, check out the official React documentation.

A Next.js Banner

Next.js was created on top of React in an effort to build an easy-to-use development framework. It was developed by Vercel (formerly Zeit) and makes use of many of the popular features of React. Right out of the box, Next.js provides things like pre-rendering, routing, code splitting, and webpack support. For more on Next.js, check out the official Next.js documentation.

What do React vs. Next.js projects look like?

With React, you can get up and running by installing Node.js on your machine and running npx create-react-app my-app. This will create a basic project structure with src/App.js file as the entry point for the application.

You’ll also have a public folder where you can store assets, and the initial scaffold includes a service worker and a method to pull in Jest for testing. The initial scaffold looks like this:

.
├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── index.css
│   ├── index.js
│   ├── logo.svg
│   ├── serviceWorker.js
│   └── setupTests.js
└── yarn.lock

With Next.js, you can get started by running npx create-next-app. This will scaffold out a project that already has a pages folder for the pages or routes and a public directory that hosts your assets. The initial scaffold looks like this:

.
├── README.md
├── package.json
├── node_modules
├── pages
│   ├── _app.js
│   ├── api
│   └── index.js
├── public
│   ├── favicon.ico
│   └── vercel.svg
├── styles
│   ├── Home.module.css
│   └── globals.css
└── yarn.lock

The files in the pages directory correlate to the routes in your application. The public directory holds your static files or images you want to serve, and it can be directly accessed — no need to use require or other traditional React methods to import pictures into components.

Within the pages directory, you’ll see an index.js file, which is the entry point of your application. If you want to navigate to other pages, you can use the router with Link, as you see here:

        <div className="header__links">
            <Link href="/">
                <a className="header__anchor">Home</a>
            </Link>
            <Link href="/about">
                <a className="header__anchor">About</a>
            </Link>
        </div>

With regards to the developer experience, the initial scaffolding process is pretty straightforward for both Next.js and React. React, however, does require you to add libraries like React Router for routing, whereas Next.js offers that functionality out of the box with the Link component.

Additionally, the overall structure of your application is already guided by Next.js by having the pages directory to hold your containers etc.

Building pages

Now we can begin to discuss real examples of React vs. Next.js with the sample application I mentioned at the beginning. Again, you can find it in the repo.

Building pages with React requires you to create a component and then pull in React Router to orchestrate transitions in your site. If you look in the react folder in the sample application, you’ll see what you would likely expect from a traditional React application:

export default function App() {
    return (
        <Router>
            <section>
                <Header />
                <Switch>
                    <Route path="/episodes">
                        <EpisodesPage />
                    </Route>
                    <Route path="/season2">
                        <Season2Page />
                    </Route>
                    <Route path="/quotes">
                        <QuotesPage />
                    </Route>
                    <Route path="/">
                        <HomePage />
                    </Route>
                </Switch>
            </section>
        </Router>
    );
}

Here, the Header, EpisodesPage, Season2Page2, QuotesPage, and HomePage are all components that React Router is routing the URL path to render.

If you look at the Next.js folder of the project, you’ll notice that the project is much leaner because the routes are all built into the pages folder. The Header component uses Link to route to the different pages, as you see here:

import Link from "next/link";
const Header = () => {
    return (
        <nav className="header">
            <span>
                <Link href="/">Home</Link>
            </span>
            <span>
                <Link href="/episodes">Episodes</Link>
            </span>
            <span>
                <Link href="/season2">Season 2</Link>
            </span>
            <span>
                <Link href="/quotes">Quotes</Link>
            </span>
        </nav>
    );
};
export default Header;

A high-level view of the Next.js project shows how easy it is to follow as well:

.
├── README.md
├── package-lock.json
├── package.json
├── pages
│   ├── _app.js
│   ├── _document.js
│   ├── components
│   │   └── Header.js
│   ├── episodes.js
│   ├── index.js
│   ├── quotes.js
│   ├── season2.js
│   └── styles
│       ├── _contact.scss
│       ├── _episodes.scss
│       ├── _header.scss
│       ├── _home.scss
│       ├── _quotes.scss
│       ├── _season2.scss
│       └── styles.scss
├── public
│   ├── HomePage.jpg
│   └── favicon.ico
└── yarn.lock

When you want to build out pages for the React project, you must build the component and then add it to the router. When you want to build pages for the Next.js project, you just add the page to the pages folder and the necessary Link to the Header component. This makes your life easier because you’re writing less code, and the project is easy to follow.

Pulling in data

With any application, you’ll always have a need to retrieve data. Whether it’s a static site or a site that leverages multiple APIs, data is an important component.

If you look in the react folder in my sample project, you’ll see the EpisodesPage component uses a Redux action to retrieve the episodes data, as you see here:

    const dispatch = useDispatch();
    // first read in the values from the store through a selector here
    const episodes = useSelector((state) => state.Episodes.episodes);
    useEffect(() => {
        // if the value is empty, send a dispatch action to the store to load the episodes correctly
        if (episodes.length === 0) {
            dispatch(EpisodesActions.retrieveEpisodes());
        }
    });
    return (
        <section className="episodes">
            <h1>Episodes</h1>
            {episodes !== null &&
                episodes.map((episodesItem) => (
                    <article key={episodesItem.key}>
                        <h2>
                            <a href={episodesItem.link}>{episodesItem.key}</a>
                        </h2>
                        <p>{episodesItem.value}</p>
                    </article>
                ))}
            <div className="episodes__source">
                <p>
                    original content copied from
                    <a href="https://www.vulture.com/tv/the-mandalorian/">
                        here
                    </a>
                </p>
            </div>
        </section>
    );

The Redux action retrieves the values from a local file:

import episodes from '../../config/episodes';

// here we introduce a side effect
// best practice is to have these alongside actions rather than an "effects" folder
export function retrieveEpisodes() {
    return function (dispatch) {
        // first call get about to clear values
        dispatch(getEpisodes());
        // return a dispatch of set while pulling in the about information (this is considered a "side effect")
        return dispatch(setEpisodes(episodes));
    };
}

With Next.js, you can leverage its built-in data fetching APIs to format your data and pre-render your site. You can also do all of the things you would normally with React Hooks and API calls as well. The added advantage of pulling in data with Next.js is just that the resulting bundle is prerendered which makes it easier for consumers of your site.

In my sample project, if you go to the nextjs folder and the episodes.js page, you’ll see that information on The Mandalorian episodes is actually constructed by the call to getStaticProps, so the actual retrieval of the data only happens when the site is first built:

function EpisodesPage({ episodes }) {
    return (
        <>
            <section className="episodes">
                <h1>Episodes</h1>
                {episodes !== null &&
                    episodes.map((episodesItem) => (
                        <article key={episodesItem.key}>
                            <h2>
                                <a href={episodesItem.link}>{episodesItem.key}</a>
                            </h2>
                            <p>{episodesItem.value}</p>
                        </article>
                    ))}
                <div className="episodes__source">
                    <p>
                        original content copied from
                        <a href="https://www.vulture.com/tv/the-mandalorian/">here</a>
                    </p>
                </div>
            </section>
        </>
    );
}
export default EpisodesPage;
export async function getStaticProps(context) {
    const episodes= [...];
    return {
        props: { episodes }, // will be passed to the page component as props
    };
}

More advanced actions

Beyond the basic functions we’ve covered here, you also eventually will need to do something more complex.

One of the more common patterns you see with React applications at scale is to use Redux. Redux is great because it scales a common method for working with your application’s state. The process of creating actions, reducers, selectors, and side effects scales well no matter what your application might be doing.

With React, this is a matter of defining a store and then building flows throughout your application. One of the first things I did was see if I could do this in my project with Next.js.

After some googling (and a few failed attempts), I found that because of the way that Next.js pre- and re-renders each page, using a store is very difficult. There are a few folks that have made implementations of Redux with Next.js, but it’s not as straightforward as what you’d see with a vanilla React app.

Instead of using Redux, Next.js uses data fetching APIs that enable pre-rendering. These are great because your site becomes a set of static pieces that can be easily read by web crawlers, thus improving your site’s SEO.

This is a huge win since JS bundles have typically been difficult for crawlers to understand. Additionally, you can be more crafty with some of these APIs and generated assets at build time like RSS feeds.

My personal blog site is written with Next.js. I actually built my own RSS feed by using the getStaticProps API that comes with Next.js:

export async function getStaticProps() {
  const allPosts = getAllPosts(["title", "date", "slug", "content", "snippet"]);

  allPosts.forEach(async (post) => {
    unified()
      .use(markdown)
      .use(html)
      .process(post.content, function (err, file) {
        if (err) throw err;
        post.content = file;
      });
  });

  const XMLPosts = getRssXml(allPosts);
  saveRSSXMLPosts(XMLPosts);

  return {
    props: { XMLPosts },
  };
}

The getAllPosts and getRssXml functions convert the Markdown into the RSS standard. This then can then be deployed with my site, enabling an RSS feed.

When it comes to more advanced features like Redux or pre-rendering, both React and Next.js have tradeoffs. Patterns that work for React don’t necessarily work for Next.js, which isn’t a bad thing because Next.js has its own strengths.

Overall, in implementing more advanced actions, the developer experience with Next.js sometimes can be more guided than you’d normally see with a React project.

Working with the documentation

With any software project, good documentation can really help you to easily use tools, understand what libraries to use, etc. There are fantastic documentation options available for both React and Next.js.

As I mentioned in the intro, Next.js has a “learn-by-doing” set of documentation that walks you through how to do things like routing and building components. React also has a similar setup, with multiple tutorials that explain the basics.

With React, you can also rely upon a great community of developers that have created content in blog posts, YouTube videos, Stack Overflow, and even the React docs themselves. This has been built over years of development as the library has matured.

With Next.js, there is less in the way of formal tutorials and more in the way of GitHub issues and conversations. As I built my personal blog site, there were times when I had to do significant googling to resolve Next.js issues. However, the team members of Next.js are themselves very accessible in the open-source world.

Tim Neutkens, one of the Next.js team members, actually responded to me directly on Twitter when I wrote a post on Next.js earlier this summer. He helped me work on an issue and was really great to work with. Having the community members that accessible is a great strength.

With the React community, there are many key members that are also just as accessible. In both React and Next.js, the active community provides for a very positive developer experience.

Final thoughts on Next.js vs. React

The developer experience is what makes engineers love what they do. I work professionally as an engineer, but the professional environment doesn’t always lend itself to the best developer experience.

In a perfect world, you’d find yourself on a great team of engineers with a great product, a strong user community, and powerful tools. In the real world, you find some of that, but usually one (or more) of those is lacking.

React and Next.js both provide great developer experiences in their own way. React lets you build things the way you want and is supported by a strong community. Next.js makes your life easier through several tools and conventions available out of the box, and it is backed by a very active open source community as well.

When it comes to the tooling itself, both React and Next.js were easy to get started. Going beyond the simple “hello world” project, it was fairly easy to find help, whether it be in the documentation or community resources.

Both React and Next.js have specific things they do well. As I’ve mentioned, I rewrote my personal blog with Next.js because I was a fan. It is a great tool to use for static sites and connects easily with the CI/CD pipeline I have set up. Additionally, it was easy to navigate when I wanted to add components to my site for the different pages.

React is a great addition to any project, and it can also scale if given the opportunity. React is more versatile than Next.js only because it is a library; it is up to the engineer to determine its implementation.

At the end of the day, both React and Next.js provide solid developer experiences. I hope the comparisons and discussions I’ve included here give some insight to how you could use them with your projects. I encourage you to check them out, and check out my sample projects as well.

Thanks for reading my post! Follow me on andrewevans.dev and connect with me on Twitter at @AndrewEvans0102.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Author: admin

Leave a Reply

Your email address will not be published.