What you need to know about Next.js

What you need to know about Next.js

Introduction

Initially, data was processed on the server and an HTML page was delivered to the client-side browser to display a web page. This worked great until more interactive content started being displayed on the web pages. Every time some new interactivity had to be handled, the whole page was re-compiled by the server. With more complex websites being made, Server-Side Rendering (SSR) became slow and inefficient.

What is Server-Side Rendering?

Server-Side Rendering with JavaScript libraries like React is where the server returns a ready-to-render HTML page and the JS scripts required to make the page interactive. The HTML is rendered immediately with all the static elements. In the meantime, the browser downloads and executes the JS code after which the page becomes interactive. The interactions on this page are now handled by the browser on the client side. For any new content or new data, the browser sends a request to the server through APIs, and only the newly required information is fetched.

In summary, Server-Side Rendering is a process where the server converts the web pages into viewable format before sending them to the browser.

Advantages

  • Fast initial loading of the web page since ready-to-display HTML is provided to the browser.

  • Great user experience even if the user has a bad connection, outdated device, or JavaScript disabled in the browser because all the basic content is ready to be rendered.

  • The content of the web page is indexed quicker resulting in better SEO ranking.

  • A great option for static pages since Server-Side Rendering loads the content promptly and efficiently.

Disadvantages

  • SSR needs more resources and can be expensive since all the processing is done on the server.

  • For complex applications, the high number of server requests can slow down the site.

  • Increased load with many users can lead to bottlenecks.

  • Setting up SSR can be complicated and tedious.

What is Next.js?

Next.js is an open-source React front-end framework that is built over Node.js which simplifies the process for SSR and adds additional optimization capabilities like Server-Side Rendering (SSR) and Static-Site Generation. Next.js builds on the React library, meaning Next.js applications take the benefits of React and just add additional features.

Some core features of Next.js are:

  • Minimal config – it provides automatic compilation and bundling.

  • Pre-rendering pages – pages can be rendered at build time or request time in a single project.

  • Built-in CSS support – option to import CSS files from a JS file.

  • Fast refresh – fast editing experience with hot code reloading.

  • Image Optimization – images are automatically optimized.

  • Typescript support – automatic configuration and compilation.

  • Static Exports – a fully static site from an app can be exported.

The basic configuration of Next.js to support SSR in a project

To use Next JS, we need to have Node.js installed on the machine which can be a Mac, Windows, or Linux system. To begin, we will create a Next app similar to creating a React app using the npx which is the command that comes bundled with Node.js.

npx create-next-app [our-app-name]

or

yarn create next-app

If you want to start with a TypeScript project you can use the --typescript flag:

npx create-next-app@latest --typescript

or

yarn create next-app --typescript

The command will initialize the Next app and create a new folder with the app name. All the required packages will be automatically downloaded and referenced in package.json.

We can immediately run the sample app by navigating to the application’s root directory and starting the npm server.

npm run dev

Isn’t this great? Within no time we already have a basic app structure and some sample code to explore.

There are so many cool features to check out in Next.js but let us focus on two of the most useful ones – pre-rendering and image optimization.

Pre-Rendering

Next uses the concept of ‘Pages’ as building blocks of the application. Each page is pre-rendered in advance resulting in the code being a fully formatted HTML document when it reaches the browser. This offers considerable improvements in performance and SEO.

For interactive pages, the generated HTML is linked with minimal JS code required for that page which is executed by the browser.

Next.js uses two forms of pre-rendering with the difference being when the HTML is generated.

  1. Static-Site Generation: Here the HTML is generated at build time and is reused on every subsequent request.

  2. Server-Side Rendering: In this form, the HTML is generated each time a request is made.

Let us create a simple sample app to practically delve into the concept. Firstly, we create a file to hold our data which we store at the root level.

There is a default index.js file that was rendered when we set up the app. Let us change the code of the index file to showcase content from our data file. This is our first page that lists the names of the winners. We render these names as links and on clicking, we will show the details for the player clicked on a separate page.

import Link from 'next/link';
import champions from '../data';

export default function Home() {
    return (
        <div style={{padding: "3rem"}}>
            <main>
                <h1>List of Tennis GrandSlam Champions with 20 titles</h1>
                <ul>
                    {champions.map((item) => (
                        <li key={item.slug}>
                            <Link href={`/champions/${item.slug}`}>
                                <a>{item.title}</a>
                            </Link>
                        </li>
                    ))}
                </ul>
            </main>
        </div>
    );
}

Now to show the content on click, we can use either of the two mentioned methods of pre-rendering. We should use the static generation method when the data is not dependent on a specific user context which is the case in our example. Nonetheless, let us work with both methods here to get an idea.

  1. Static-Site Generation: by using this method, all the HTML pages are constructed in advance at build time. This is done using the inbuilt functions getStaticProps and getStaticPaths. This provides the best performance with cached Pages and makes your site more SEO friendly.

Since we want to create the pages based on the data, we create a file called [id].js which indicates that it is a dynamic route file.

import { useRouter } from 'next/router';
import champions from '../../data';

// This gets called at build time
export async function getStaticProps({ params }) {

    const championData = champions.find((item) => item.slug === params.id);

    // Pass data to the page via props
    return { props: { championData, timestamp: (new Date()).toUTCString() } };
}

// This gets called at build time
export async function getStaticPaths() {
    // Get the paths we want to pre-render
    const paths = champions.map((champ) => ({
        params: { id: champ.slug },
    }))

    // We'll pre-render only these paths at build time.
    return { paths, fallback: false };
}

const Champion = ({ championData, timestamp }) => {
    const router = useRouter();
    if (!championData) {
        return <h1>Champion does not exist.</h1>;
    }
    return (
        <div>
            <h1>{championData.title}</h1>
            <p>{championData.description}</p>
            <h5>Last updated at: {timestamp}</h5>
        </div>
    );
};
export default Champion;
  1. Server-Side Rendering: the code for this method is similar to the static generation code, the difference being the function used which is getServerSideProps.

Note that we have a timestamp field in our code to indicate the time of the page build. When using static generation, this timestamp will show the time of the last build. In SSR, it will change every time we click, demonstrating how the page is rebuilt each time.

import { useRouter } from 'next/router';
import champions from '../../data';

// This gets called every time the page is called
export async function getServerSideProps({ params }) {

    const championData = champions.find((item) => item.slug === params.id);

    // Pass data to the page via props
    return { props: { championData, timestamp: (new Date()).toUTCString() } };
}

const Champion = ({ championData, timestamp }) => {
    const router = useRouter();
    if (!championData) {
        return <h1>Champion does not exist.</h1>;
    }
    return (
        <div style={{ padding: "3rem" }}>
            <h1>{championData.title}</h1>
            <p>{championData.description}</p>
            <h5>Last updated at: {timestamp}</h5>
        </div>
    );
};
export default Champion;

Image Optimization

Another superb feature of Next.js is automatic image optimization that prevents sending large images to small screens and provides support for modern formats like WebP.

Some of the optimizations built into the Image component include:

  • Improved Performance: Always serve correctly sized images for each device, using modern image formats

  • Visual Stability: Prevent Cumulative Layout Shift automatically

  • Faster Page Loads: Images are only loaded when they enter the viewport, with optional blur-up placeholders

  • Asset Flexibility: On-demand image resizing, even for images stored on remote servers

import Image from 'next/image'
import profilePic from '../public/me.png'
function Home() {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src={profilePic}
        alt="Picture of the author"
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}

export default Home

Conclusion

Next.js is a powerful, easy-to-use framework that prepares your React app for production at scale. We’ve looked at some of its most useful and popular features like Server-Side Rendering, Static-Site Generation, and Image optimization.

Feel free to connect with me on LinkedIn and Twitter.