macbook coding
26 Jul 2021 19 min to read

Server-Side Rendering in React using Next.js – How it Works & Implementation Example

With advances in web technology, the way we represent and consume information on the internet has changed drastically over the years.

Initially, everything 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.

This gave rise to the client-side rendering process where the browser renders the HTML page by modifying DOM (Document Object Model). For any interactivity, the browser does not need to contact the server since all the code is run on the client-side. With JavaScript libraries like React becoming popular, client-side rendering became the norm. But, in this scenario, the user needs fast internet and an up-to-date browser to avoid issues in displaying the content. Also, the web page is rendered only once everything is executed making the initial loading slower and not SEO friendly.

Now, what if we could load the web page with SSR and then dynamically fetch only the necessary content that is required rather than re-compiling the whole page?

That is exactly what can be done these days which had made server-side rendering gain traction again! So, let us understand what Server-Side rendering in React JS is, the pros and cons of using it and how to go about setting it up.

 

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 brief, SSR is a process where the server converts the web pages into viewable format before sending them to the browser.

Server Side Rendering React

Server-Side Rendering requires using a little more server power, but it gives the end user’s browser less work to do. / source: omnisci.com

 

What are the advantages and disadvantages of SSR in react?

To decide whether to use Server-side rendering in React.js, let us look at some of the benefits and drawbacks of SSR.

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.

 

Impact of SSR on SEO and performance

Today we know that Search Engine Optimization is crucial to driving traffic to web pages. Right now, most search engines other than Google are inadequate for rendering pages before indexing. Even with Google, there are issues in the navigation of sites with client-side rendering.

In server-side rendering, all the elements required for SEO are available in the initial response. Moreover, webpages rendered on the server-side are more accurately indexed since browsers prioritize pages that load faster.

In short, your site will be ranked higher in the search results if it is rendered on the server-side.

On social media platforms, linking to websites with server-side rendering is better for proper representation of the title and thumbnail of the site.

When talking about performance, there are three major parameters that we need to consider:

1. TFB (Time to First Byte) – the amount of time between a link clicked and the first bit of content is received.

2. FCP (First Contentful Paint) – the moment when some requested content is rendered.

3. TTI (Time To Interactive) – the time when the page becomes interactive.

TTFB can be more with server-side rendering since a fully rendered HTML page is sent to the browser, but FCP is much faster which contributes to improved performance and user-friendliness. TTI can depend on the complexity of the web page and how many scripts need to run for rendering it. Most pages have moderate level interactivity which results in low TTI when rendering server-side.

 

What is required in the environment to run SSR?

The first and foremost requirement for server-side rendering is to have a runtime environment that can implement a web server and handle events.

Node.js is one of the most popular frameworks to set up SSR for a React application and Express is a great option for creating the HTTP server. Next, we need a JavaScript compiler like Babel and a JS module bundler like Webpack, Rollup or a similar tool.

 

SSR schematic representation

A schematic representation of SSR application / Based on the source – https://dev.to/alexsergey/server-side-rendering-from-zero-to-hero-2610

 

Let us see briefly how to set up a simple React JS website with server-side rendering using Express.js. The configuration steps are along the following lines:

1. Create a new folder for the React app.
2. Install the dependencies like Babel.
3. Configure the dependencies installed and set up the packages used by the server.
4. Move all the code to the client directory and create a server directory.
5. Create basic server code with express.
6. Transpile all the code
7. Test the server code.
8. Configure webpack or any module bundler.
9. Build the code.
10. Test and debug.

This can quickly get complex and create issues regarding styles, images, routing, browser history, and so on.

All in all, there are quite a few libraries and packages to install and configure when setting up server-side rendering on your machine, and it can be tiresome to implement everything ourselves.

That is where something like Next.js can help us!

 

What is Next.js?

Next.js is a framework that provides a common structure for the front-end development of React apps with zero configuration and a single command toolchain. It provides functionalities to create React-based applications with transparent handling of server-side rendering.

Next.js is an open-source development framework built over Node.js that simplifies the process for SSR and offers many other useful 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.
  • Analytics – it provides a mechanism to measure performance.
  • Automatic Code Splitting – only the necessary libraries and script files required at a time are rendered.
  • Dynamic Components – JavaScript modules and React Components can be imported dynamically.
  • Static Exports – a fully static site from an app can be exported.

 

nextjsbenefits

Using next.js benefits the user experience, and that always leads to better business results. / source: pagepro.co

 

Basic configuration of Next.js to support SSR in a project

The only thing required for using Next JS is having Node.js installed on the machine that can be a Mac, Windows, or Linux system. Once we have Node installed and updated to the latest version, we are ready to start our journey with Next.js.

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]

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 absolutely 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 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.

export default [
  {
    slug: 'roger-federer',
    title: 'Roger Federer',
    description: 'The Swiss Maestro holds 8 Wimbledon titles, 6 Australian Open wins, 5 US Open wins and just 1 French Open title.'
  },
  {
    slug: 'rafael-nadal',
    title: 'Rafael Nadal',
    description: 'The Spanish sensation holds 13 French Open titles, 2 Wimbledon titles, 4 US Open wins and just 1 Australian Open title.'
  },
  {
    slug: 'novak-djokovic',
    title: 'Novak Djokovic',
    description: 'The current world no 1 recently added his 6th Wimbledon win to his 9 Australian Open titles, 3 US Open wins and 1 French Open title.'
  }
]

 

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 the methods here to get an idea.

1. Static 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.

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;

2. 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;

On creating a new build, we can see the pre-rendered files in the folder.

folder view

 

Check out here how the app looks like on the browser when we run the code.

 

pre render example

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. Moreover, the images are optimized on-demand, so image optimization works with images from any source. This drastically improves performance as the build time is not increased even when there are a lot of images. We can also configure to use cloud providers like Imgix and Cloudinary among others to optimize images.

 


import Image from 'next/image'

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>
    </>
  )
}

export default Home

Summary

As the world evolves, we need to keep up with the fast-paced changes in technology. Server-side rendering is an excellent option for rendering web pages to increase the initial page load speed, improve SEO and provide a better user experience.

The best part about web technology is the availability of platforms and frameworks that make complex concepts easier to implement. Next.js provides a fantastic developer experience with out-of-the-box features for production without difficult configuration.

 

Developer Team
Need to implement SSR in your application?


    Share

    SUBSCRIBE our NEWSLETTER

    Are you interested in news from the world of software development? Subscribe to our newsletter and receive a list of the most interesting information.

      ADD COMMENT

      RECOMMENDED posts