#100DaysOfMERN - Day 60

#100DaysOfMERN - Day 60

Next.js -> React rendered on the Server

·

7 min read

✏ getStaticProps

Next.js is most powerful when it can create the pages at build time (before a user request occurs). During the build process, the pages are pre-rendered and stored on disk, so they can instantly be served upon a request.

If you have a page with never changing content like "About", you don't need getStaticProps at all, you'd just create a file in the pages folder. But if you ever want to update the content, you'd have to deploy a new version of your website - unless you make use of the powers of getStaticProps.

Understanding what that function does, how it works, where to use it, and how to configure it is key to optimising a website for performance.


Static page without external data

I'll start with the simplest example - a static TOS page:

export default function TermsOfService(){
  return (
    <>
      <h1>Terms Of Service</h1>
      <div>Hard coded content</div>
    </>
  )
}

It has only hard-coded content in it, and it's unlikely that it would have to be updated every week - no need for getStaticProps at all.


Static page with external data

Oftentimes, you don't hardcode the content. It would come from a database, but it wouldn't change over time - for example, you're displaying a page with information about your favourite movie. In this case, you can use getStaticProps without any configuration - you'd use it like getServerSideProps:

movie.js

export default function Movie({ movie }){
  return (
    <>
      <h1>{movie.title}</h1>
      <div>{movie.summary}</div>
    </>
  )
}

export const getStaticProps = async () => {
  const result = await fetch('url-to-movie-database/movie');
  const movies = await result.json();
  return { props: { movie } }
}

Unlike getServerSideProps, the function only gets called at build time, fetches the data, feeds it into the component, and the page is pre-rendered and saved.

This works well if you only have few pages, but what if you have hundreds of movies? Again, the data wouldn't change, but having static routes for those would mean to have hundreds of files in your pages folder - madness.

Instead, you'd have one file [movie].js as a template, and make use of getStaticPaths as an addition to getStaticProps.


✏ getStaticPaths

In order to pre-render all of those hundreds of pages, their paths and the data they display must be known ahead of time. Instead of hard-coding a single URL to fetch the data for one movie like in the above example, you use the context object that is passed to getStaticProps to get the movie's id/slug/whatever you choose as identifier for the routes.

Static page with external data on dynamic routes, pre-rendered at build time

The template would look something like this - a component that needs a movie object, and getStaticProps to fetch that object:

[movie].js

export default function MovieTemplate({ movie }){
  return (
    <>
      <h1>{movie.title}</h1>
      <div>{movie.summary}</div>
    </>
  )
}

export const getStaticProps = async ( context ) => {
  const result = await fetch(`url-to-movie-database/${context.params.slug}`);
  const movies = await result.json();
  return { props: { movie } }
}

To build all paths in advance, you need to add getStaticPaths. That function fetches all movies from the database, maps over them to extract the slugs for each, and returns an array of objects, one for each path:

export const getStaticPaths = async () => {
  const result = await fetch('url-to-movie-database/movies');
  const movies = await result.json();
  const paths = movies.map(movie => ({ params: { slug: movie.slug } }));
  return { paths, fallback: false }
}

(The fallback value is required, see below. Setting it to false means that non-existent routes will lead to a 404 page.)

Creating all those routes in advance will increase the build time, which is acceptable for small and mid-sized websites. But what if you wanted to migrate Wikipedia to Next.js? Building all those pages and routes in advance would again be absolute madness.


✏ Building at Request

For a massive site like Wikipedia (let's assume for now that the content isn't editable by users and wouldn't change over time), you wouldn't create all routes and pre-render all pages at build time. Instead, it's perfectly fine to return an empty paths array in getStaticPaths, while setting the fallback to either true or blocking:

export const getStaticPaths = async () => {
  return { paths: [], fallback: true }
}

What happens now if someone hits a route that hasn't been created yet? Next.js will run getStaticProps for this route once, and save the resulting pre-rendered page. This means that the first user trying to access the route will get a little delay, but for all subsequent requests, the page is immediately available.


If a certain route never gets hit, does the page even exist? ⁉ 💭


fallback: true vs fallback: 'blocking'

If fallback is set to true, this will be reflected by the router object. While the content is pre-rendered in the background, it allows to show a loading spinner in the meantime, so the user doesn't see a completely blank page:

export default function ArticleTemplate({ article }){

  const router = useRouter();

  if (router.isFallback) {
    return <div>Loading...</div>
  }

  return (
    <Article article={ article } />
  )
}

Setting fallback to "blocking" means that nothing is shown. This might be preferrable if the page generation doesn't take much time, in which case the user would otherwise see a "Loading..." message that almost immediately disappears/flashes.

This strategy of "pre-rendering at first request" is a great solution for large websites with many routes, while keeping the build times short. But what if the content for a route might change over time, like it constantly does on Wikipedia when someone edits an article?


✏ Incremental Static Regeneration

Similar to building routes "at request", you can configure getStaticProps to re-build the page after a certain time interval, to create a new updated version of the page:

export const getStaticProps = async ( context ) => {
  const result = await fetch(url);
  const data = await result.json();
  return { props: { data }, revalidate: 3600 }
}

The time unit is seconds, so with this configuration, Next.js would pre-render the page once at time index 0, and serve that page for all subsequent requests. The first user to hit the page after the 1 hour revalidation time has passed would still see that version, but in the background, Next.js would create a new page and replace the old one.

This doesn't mean that there's a timer running on the server. If nobody ever requests that page again, it'll forever stay in its original version. Only a new request can trigger a re-build.

The time interval very much depends on the nature of the site, the number of users hitting a certain route per time interval, how often the content updates, etc. A large news site with lots of hits per second would have a short interval (1 second isn't unusual). If you have 1000 hits per second, that would be 999 users benefitting from the pre-rendered page that the first user in that time interval had triggered.

For a photo image gallery that frequently shows the newest pictures, but doesn't have to be exactly up-to-date, a time interval of 10-60 minutes would be perfectly acceptable.


✏ Why use getServerSideProps at all then?

The powers of getStaticProps and getStaticPaths should hopefully be obvious now, but there are use cases where they're not really applicable. If you take this very blog (or facebook if you must) as an example, every user sees a different page. It's not just that they see a different avatar, but depending on their interests, they're also shown a different selection of articles. The pages aren't reusable for large amounts of other users, so you wouldn't benefit from the advantages of getStaticProps.


✏ Development vs. Production Mode

One important thing to keep in mind: In development mode, getStaticProps and getStaticPaths behave like getServerSideProps and runs on every request.


✏ Resources

Advanced Next.js Course (codedamn)

Client-Side VS Server-Side Rending - Data Fetching with Next.js


✏ Thanks for reading!

I do my best to thoroughly research the things I learn, but if you find any errors or have additions, please leave a comment below, or @ me on Twitter. If you liked this post, I invite you to subsribe to my newsletter. Until next time 👋


✏ Previous Posts

You can find an overview of all previous posts with tags and tag search here:

#100DaysOfMERN - The App