Skip to content

Next.js and React.js: 5 Differences to Help You Make Your Choice

I should start by saying that I'm not a fulltime React.js or NextJS dev. My main technology is Angular, and I have been using it since I started coding in 2016.

But I am enthusiastic about how other frameworks and libraries work. So with that being said, I tried out some courses over the past few months, and this is why I prefer Next.js over React.js.

Automatic Code Splitting

Of course, you can perform this by lazy loading some components on React, but in NextJS it comes out of the box.

We just need to run npm run build, and the engine will create the proper files just to load those components.

➜ npm run build

> test-blog@0.1.0 build /Users/daianscuarissi/repos/test-blog
> next build

info  - Checking validity of types
info  - Creating an optimized production build
info  - Compiled successfully
info  - Collecting page data
info  - Generating static pages (3/3)
info  - Finalizing page optimization

Page                                       Size     First Load JS
β”Œ β—‹ /                                      5.56 kB        76.5 kB
β”œ   β”” css/149b18973e5508c7.css             655 B
β”œ   /_app                                  0 B            70.9 kB
β”œ β—‹ /404                                   194 B          71.1 kB
β”” Ξ» /api/hello                             0 B            70.9 kB
+ First Load JS shared by all              70.9 kB
  β”œ chunks/framework-e70c6273bfe3f237.js   42 kB
  β”œ chunks/main-a054bbf31fb90f6a.js        27.6 kB
  β”œ chunks/pages/_app-ca6aae25bc99a05f.js  491 B
  β”œ chunks/webpack-69bfa6990bb9e155.js     769 B
  β”” css/27d177a30947857b.css               194 B

Ξ»  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
β—‹  (Static)  automatically rendered as static HTML (uses no initial props)

And is really cool because we don't need to add extra-code to make this happen. We don't need to configure anything else to deliver what the user needs to see that page. Awesome.

Image Optimization

The thing I love most about NextJS is Next/Image. If you ever work with images in web development, you probably know you need some configuration to lazy load the images. Well, simmilar when code-splitting in NextJS, you only need to use:

  import Image from 'next/image'

Some of the benefits that are mentioned in the documentation include:

  • Improved Performance
  • Visual Stability: Prevent Cumulative Layout Shift automatically
  • Faster Page Loads
  • Asset Flexibility

This sounds really awesome, but what about using it in a real project?

Another cool feature is you can use this for external or internal images. (If you are using external images, you need to configure the domains for each image. But it seems to be a good method for optimizing configuration.)

  import Image from 'next/image'

  export default function Home() {
    return (
      <>
        <h1>My Homepage</h1>
        <Image
          src="/me.png"
          alt="Picture of the author"
          width={500}
          height={500}
        />
        <p>Welcome to my homepage!</p>
      </>
    )
  }

BONUS:

Recently, I discovered that you can use the flag priority, and then NextJS will try to prioritize the image for loading (e.g. through preload tags or priority hints), leading to a meaningful boost in Largest Contentful Paint.

  import Image from 'next/image'

  export default function Home() {
    return (
      <>
        <h1>My Homepage</h1>
        <Image
          src="/me.png"
          alt="Picture of the author"
          width={500}
          height={500}
          priority
        />
        <p>Welcome to my homepage!</p>
      </>
    )
  }

Many ways to generate the site

NextJS can perform multiple server rendering strategies from a single project, which I was impressed with when I used it for the first time.

  • Static Generation (SSG)

Basically, using this approach, we render all pages at build time.

Every component can implement a method called getStaticProps() through which we can fetch data from a DDBB, and then return it as props to the component and use it there.

These can be mixed to serve your site through a CDN where it can be stored in cache. This works pretty well if you are building a blog, or just a static site, where the data doesn't change often.

  export async function getStaticProps() {
    const res = await fetch('https://...');
    const data = await res.json();

    return { props: { data } };
  }
  • Server-side Rendenring (SSR)

What if our data changes often? Well, in that case, you can use this approach, which builds the HTML page at request time every time it is requested by the user.

In the component, we can use the method getServerSideProps() and then pass as props to the component. So the difference with SSG is every time the user enters the page, the backend fetches the latest, and returns that to the page.

  export async function getServerSideProps() {
    const res = await fetch('https://...');
    const data = await res.json();

    return { props: { data } };
  }

  • Incremental Static Generation (ISG)

This feature allows the regeneration of static pages during runtime. It's a hybrid solution of SSR and SSR.

The page is generated on the first request. Unlike in SSR, where the visitor has to wait for the data fetching, a fallback page is served immediately.

During the fallback stage, we can present placeholders and a skeleton page until everything is resolved. Skeleton pages are a common pattern that you can see almost everywhere.

Once the data is resolved, the final page is cached, and visitors will receive the cached version immediately, just like with SSG. We can also set when Next.js should re-validate the page and update it.

  export async function getStaticProps() {
    const res = await fetch('https://...');
    const data = await res.json();

    return { props: { data }, revalidate: 60 };
  }

Routing

In other frameworks like Angular or React, we need to define the routes in order to serve certain files, components, etc. In Angular, these routes are defined in modules, and in React, we can use react-router.

But, again in NextJS, we have support for routes out of the box, we just need to create a file with extension .js, .jsx, .ts, .tsx under pages folder:

  next-app
  β”œβ”€β”€ node_modules
  β”œβ”€β”€ pages
  β”‚   β”œβ”€β”€ index.js // path: base-url (/)
  β”‚   β”œβ”€β”€ books.jsx // path: /books
  β”‚   └── book.ts // path: /book
  β”œβ”€β”€ public
  β”œβ”€β”€ styles
  β”œβ”€β”€ .gitignore
  β”œβ”€β”€ package.json
  └── README.md

But what about dynamic or nested routes? Yes, these are supported, again, out of the box.

  pages/blog/[slug].js β†’ /blog/:slug (/blog/hello-world)
  pages/[username]/settings.js β†’ /:username/settings (/foo/settings)
  pages/post/[...all].js β†’ /post/* (/post/2020/id/title)

API Routing

Another feature I really enjoy using was the API Routing.

Any file inside the folder pages/api is mapped to /api/, and will be treated as an API endpoint instead of a page. They are server-side only bundles, and won't increase your client-side bundle size.

For example, the following API route pages/api/user.js returns a json response with a status code of 200:

  export default function handler(req, res) {
    res.status(200).json({ name: 'John Doe' })
  }

And, if you already have an API, NextJS already can work with that. According to NextJS docs:

  • Masking the URL of an external service (e.g. /api/secret instead of https://company.com/secret-url)
  • Using Environment Variables on the server to securely access external services.

BONUS πŸ”₯:

On-demand Incremental Static Regeneration (Beta)

This feature was recently included in the last update from the Vercel Team.

According to the team, we can now use a method called unstable_revalidate(), allowing developers to revalidate individual pages that use getStaticProps.

  // pages/api/revalidate.js
  export default async function handler(req, res) {
    // Check for secret to confirm this is a valid request
    if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
      return res.status(401).json({ message: 'Invalid token' })
    }

    try {
      await res.unstable_revalidate('/path-to-revalidate')
      return res.json({ revalidated: true })
    } catch (err) {
      // If there was an error, Next.js will continue
      // to show the last successfully generated page
      return res.status(500).send('Error revalidating')
    }
  }

This makes it easier to update your site when:

  • Content from your headless CMS is created or updated
  • Ecommerce metadata changes (price, description, category, reviews, etc.)