This blog will load fast. Why?

Code
By: Mateo Lewinzon / Published on Mar 8, 2023
- views6 min read

The problems with traditional React data-fetching

When it comes to building web applications with React, there's a common problem that many developers face: data fetching. In a typical React app, when a user first enters a page, the browser first loads an empty HTML file. Then, the JavaScript code that fetches the data from the server is executed, and finally, the data is rendered on the screen. This traditional approach has some problems.

Empty content while fetching data

First, this process can take some time, and the user may see a blank page or loading spinner during this process, which can be frustrating.

This problem can be even more pronounced when dealing with complex data fetching, such as fetching data from multiple APIs or databases. In these cases, the time required to load the page can be even longer. Even though this can be mitigated using UI elements such as loading spinners, it leads to a poor user experience.

Imagine a blog article page like this one, using the traditional approach of fetching the content with useEffect.

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

function Article() {
  const [article, setArticle] = useState(null);

  useEffect(() => {
    async function fetchData() {
      const response = await fetch('https://api.example.com/articles/1');
      const data = await response.json();
      setArticle(response.data);
    }
    fetchData();
  }, []);

  if (!article) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
    </div>
  );
}

export default Article;

Once the initial HTML is served, the website is empty until it's done fetching the article.

Even if it is a couple of milliseconds, the content would suddenly flash into the screen. There is a better way, believe me.

Bad SEO

This approach also takes a toll on Search Engine Optimization. If the HTML that's first served to the client is empty, then search engines will have a hard time crawling your website. Perhaps this is irrelevant to your application, for instance, if you are building a back office app for internal use. But for most cases, an optimized SEO is important and it doesn't quite come out of the box in React.

Server rendering: The modern way of fetching data

The problems above can be solved by implementing server-rendering methods. With these methods, unlike client-side rendering (example above), the served HTML will already be populated with content when it arrives to the client.

The whole rendering happens in the server, so the data will be fetched and the content will be ready as soon as the page loads. No more spinners or flashing content. Also when it comes to SEO, the engines will crawl into the websites with ease since they will encounter already populated HTML.

Next.js is a popular framework that, among other things, has built-in support for server rendering methods.

This is how each of the methods works:

Server Side Rendering (SSR)

The server renders the page and sends a fully-formed HTML file to the client, every time it is requested. This is commonly referred to as an "on-demand" server-render. SSR is great, but it can be slow and resource-intensive, especially for large-scale applications.

If the content is constantly updated (eg. a social media feed), or if it requires or is dependent on authentication (eg. "My Account" page), SSR is the way to go. When the requested page requires server-side code to be executed on EVERY request, this will always be the best option

However, as you will see, SSR is not necessary for content that only consumes static data or pages that do not change often. Take this article page for example. It wouldn't make sense to render the article in every request. If it won't change, why not render it only once?

Static Site Generation (SSG)

A better solution is to use Static Site Generation (SSG), which generates the HTML files ahead of time during the build process. This means that when a user requests a page, the HTML file is already pre-rendered and can be served immediately, without any data fetching or processing on the server. This results in a much faster and smoother user experience, as the page appears instantly, with no delay or loading spinner.

The HTML files from this website (including each blog article) are generated in the server when the application is built so that they are ready the instant they are requested.

Incremental Static Regeneration (ISR)

Next.js also provides support for Incremental Static Regeneration (ISR), which on top of SSG, allows pages to be re-generated on-demand, based on the data changes. This is useful for static content that still has some updates, such as a blog or news site. With ISR, the page is initially pre-rendered during the build process, but can then be updated in the background when new data is available, ensuring that the user always sees the latest information.

My blog list page uses ISR. It's re-rendered when it recieves it's first request after a minimum of 24hs. Meaning that every day some request will revalidate the data making an on-demand server render, and storing the updated HTML for future requests.

Since I'm constantly updating the site (hence re-rendering the SSG sites at builds), I'm fine with this approach, but a fancier approach could be setting an endpoint that recieves a revalidation signal. For example:

GET /api/posts/regenerate?secretAdminPassword=123

Every time we post a new blog, we send this request to trigger a regeneration and keep the HTMLs updated at all times, avoiding server renders.

Fre-fetching

On top of the server-rendering methods, Next.js allows us to easily pre-fetch the internal links visible on our page. When this happens for SSG, Next.js will fetch the whole page ahead, so when the link is clicked, it is incredibly smooth.

Conclusion

So, to summarize, server rendering is a powerful tool that can greatly improve the performance and user experience of your React-based web applications. By using SSG, you can pre-render pages during the build process, eliminating the need for server-side data fetching and greatly reducing the load time. And with support for ISR, you can keep your pages up-to-date with the latest data, without sacrificing performance or user experience.