Max Next – Section 5 – Page Pre-Rendering & Data Fetching


Courses

Updated Mar 8th, 2022

Table of Contents

Chapter Details

Chapter 84 – Module Introduction

NextJS is NOT Just About Routing. NextJS helps with building “fullstack React Apps.” By default, NextJS pre-renders all pages (~server-side rendering: SSR)

Chapter 85 – The Problem With Tradition React Apps (and Data Fetching)

Example Loading data using fetch in a useEffect that stores in useState. The original html sent by the server does not contain the data. Disadvantages are the user needs to wait (still super fast but could take a second or so in which the user sees a loading spinner before the data arrives.). Another disadvantage is SEO, where search engines need to see the data so they can index it. Not a problem where users need to log in or you have a admin dashboard for instance.

Chapter 86 – How NextJS Prepares & Pre-renders Pages

NextJS returns a pre-rendered page with the html and all the data that might be needed in advance. Will then Hydrate with React code once loaded so we still have a interactive page or app. It’s just the initial page visited which is pre-rendered. Two forms of pre-rendering, 1.) Static Generation (Recommended where possible) is where all pages are built in advance and 2.) Server-side Rendering pages are created “just in time”, after deployment, when a request reaches the server.

Chapter 87 – Introducing Static Generation with “getStaticProps”

Pre-generate a page (with data prepared on the server-side) during the build time. Pages can be cached by the server/CDN serving the app. How do we tell NextJS which pages and which data? A special function from inside the pages components (available only within pages components).

export async function getStaticProps(context) { ... }

This function is async, meaning that it returns a promise. Inside this function you run server-side code, not client-side code. Don’t have access to certain client-side API, like the window object for example. Code you write inside of this function will never be sent to and seen by the client. Can safely write code with database credentials.

Chapter 88 – NextJS Pre-renders by Default!

Even with no data-fetching function, NextJS pre-renders all pages by default

Chapter 89 – Adding “getStaticProps” to Pages

Showed an example of grabbing dummy data from a file in a “/data/dummy-backend.json” file. We want to grab data without a “useEffect” and http request. “getStaticProps” prepares props for the component and so must return an object with a props key.

Chapter 90 – Running Server-side Code & Using the Filesystem

A more realistic example loading data when the page is prepared but since we are in a “getStaticProps” function, can write server-side code to say access the file-system, and will do so using node’s built-in “fs” module. Note that the “fs” import will be stripped out. The example also uses the “path” module. Also note that if using process.cwd(), which points to the curent working directory, what is in the “pages” folder will be treated as being in the project’s root folder, due to some NextJS magic.

export async function getStaticProps() {
  const = filePath = path.join(process.cwd(), 'data', 'dummy-backend.json')
  const jsonData = await fs.readFile(filePath)
  const data = JSON.parse(jsonData)

  return: {
    props: {products: data.products},
  }
}

Chapter 91 – A Look Behind the Scenes

Run “npm run build” to build your site and we get some information in the terminal. Preview production-ready page/site by running the “npm run start” command to view on your own machine.

Chapter 92 – Utilizing Incremental Static Generation (ISR)

The “getStaticProps” server-side code running on the server-side is only partially correct, it doesn’t run on the actual server that runs our application, instead it runs on our machine when the site or application is built with Next using the “npm run build” command. One potential downside is when you have data that changes fairly frequently, like blog posts getting added over time. You could manually build after each update using the “npm run build” command although this isn’t great. NextJS solution number one is you could build your page and then also include standard react code with “useEffect” to fetch updating data from the server. NextJS solution number two, oftentimes better, uses incremental static generation to re-run build every so often continuously, “re-generate it on every request at most every X seconds.” Implement by adding a “revalidate” key to the return object in “getStaticProps.”

Chapter 93 – ISR: A Look Behind the Scenes

Adding a console.log((Re-)Generating…) to the “getStaticProps” will show you that the server is re-building on every page refresh but not more that every 10 seconds (if you refresh three times in 10 seconds it will only rebuild once).

Chapter 94 – A Closer Look At “getStaticProps” & Configuration Options

There are two other keys (besides props and revalidate) you can set on the return object inside of a “getStaticProps” function. These are “notFound” and “redirect.” Setting “notFound,” which wants a boolean, to true will render the 404 page. Why would we want to do this? If a data fetch fails. The “redirect” key will redirect obviously and may also be used if a data-fetch fails. If no data in general then redirect to a specific route.

Chapter 95 – Working With Dynamic Parameters

Every product has a clickable link that takes us to a “product-detail” page. Use “getStaticProps” and the context parameter object to find the concrete value needed for the URL. Look at the “params” key inside of the context object. “Params” is an object filled with key/value pairs where the keys are the identifiers for the dynamic path segments.

export async function getStaticProps(context) {
  const {params} = context
  
  const productId = params.id
}

Note that we can also find these “params” using the useRouter hookinside of the component function which we saw before using “router.query.eventId.” There is a difference though, if doing it in the component function this only happens in the browser. If you want to pre-render with the help of “getStaticProps” this happens on the server or during the build process and runs “before” the component function runs.

Chapter 96 – Introducing “getStaticPaths” for Dynamic Pages

If you have a dynamic file then the default is to not pre-generate the page. This is because we will have multiple pages but at this point NextJS does not know which concrete values it needs to generate pages for.

Chapter 97 – Using “getStaticPaths”

export async function getStaticPaths() {
  //goal of this fn is to tell NextJs which paths to generate
  return {
    paths: [
      {params: {pid: 'p1'}},
      {params: {pid: 'p2'}},
      {params: {pid: 'p3'}},
    ],
    fallback: false
  }
}
// but we know this is unrealistic and should instead load paths programmatically.

Chapter 98 – “getStaticPaths” & Link Prefetching: Behind the Scenes

Run “npm run build” command and we see it pre-rendered a couple pages, (index.js, )

Chapter 99 – Working With Fallback Pages

The “fallback” key can help you if you have a lot of pages that need to be pre-generated, (like Amazon). Generating rarely-visited pages helps pre-render highly-frequented pages. With fallback set to true this is possible. With “fallback” set to true even pages not listed can be valid, but built just in time, when visited. But this doesn’t happen instantly so we need to be prepared to return a fallback state in our component function. If not we will get a nasty error.

if (!loadedProduct) {
  return <p>Loading...</p>
}

An alternative is we don’t set “fallback” to true but to a string of “blocking” which will hold page until the page is finished building then it will navigate you there. This will seem like there is a lockup but we won’t need the fallback state. May have a use case to prevent showing an incomplete page.

Chapter 100 – Loading Paths Dynamically

export async function getStaticPaths() {
  const data = await getData()
  const ids = data.products.map((product) => product.id)
  const pathsWithParams = ids.map((id) => ({params: {pid: id} }))

  return {
    paths: pathsWithParams,
    fallback: 'blocking' 
  }
}

Chapter 101 – Fallback Pages & “Not Found” Pages

Requesting a page that does not exist. In “getStaticPaths” have “fallback” key set to true. NextJS tries to render a page that is not there. Need a fallback scenario in the component function like we saw before but we still get an error after briefly seeing loading because we fail to get the actual data. So we need a fallback in the “getStaticProps” function to show the 404 page if the route is not found. We still get loading briefly but then see the 404 page.

if (!product) {
  return {notFound: true}
}

Chapter 102 – Introducing “getServerSideProps” for Server-side Rendering SSR

There are two forms of Pre-Rendering: Static Generation and Server-side Rendering. In the “getStaticProps” and “getStaticPaths” we do not get access to the incoming request object. This is fine because we often don’t need access. But sometimes you do (for cookies, for user-submitted data) and this is where the async function of “getServerSideProps” comes in because you can run “real server-side code.”

Chapter 103 – Using “getServerSideProps” for Server-side Rendering

In a “user-profile.js” page component we want user-specific data and show on the screen but this is a case we cannot pre-build because we need to know for which user we are rendering this for. We could render for each id using [id] but then anyone can see those routes in the browser. We could use the help of a cookie and this is stored on the request object.

The return object in “getServerSideProps” should still be the same; it should have a “props” key, can have a “notFound” key and can have a “redirect” key but it cannot have “revalidate” key.

The code in a “getServerSideProps” function is only executed on the server after deployment (also on dev server).

Chapter 104 – “getServerSideProps” and its Context

Unlike the “context” in “getStaticProps” we get access to the request object and the response we could send back (to add extra headers if we wanted). We still get access to “params” but now also “req” and “res,” (these are the default objects from node.js).

Another reason to use GSSP is to ensure it runs for every request, and is never built in advance, never statically generated, because you have highly dynamic data that changes multiple times per second and therefore any old page you would be serving would already be outdated.

Chapter 105 – Dynamic Pages & getServerSideProps”

If you have a [uid].js file in the pages folder we cannot and do not need to use “getStaticPaths” because we can extract “params” from context. We don’t need to know which paths to pre-generate pages for because there is no pre-generation this happens for every request instead.

export async function getServerSideProps(context) {
  const {params} = context
  
  const userId = params.uid

  return {
    props: {
    id: 'userid-' + userId
    }
  }
}

Note you cannot have multiple dynamic “[].js” files in the pages folder or you will confuse NextJS so you need to move into there own folder.

Chapter 106 – “getServerSideProps”: Behind The Scenes

Lambda symbol signifies a page is not pre-generated but pre-rendered on the server only. Add a console.log(‘Server Side Code’) to prove it executes not on build but on the page load on the fly.

Chapter 107 – Introducing Client-Side Data Fetching (And When to Use It)

The opposite of preparing data on the server during the build process. Some data doesn’t need to be pre-rendered:

Pre-fetching the data for page generation might not work or be required. “Traditional” client-side data fetching (ex: useEffect() with fetch() is fine)

Chapter 108 – Implementing Client-Side Data Fetching

Using an example from traditional react app with “last-sales.js” file in the pages folder. Uses firebase for dummy backend data (database with API) and manually adds some sales entries.

import { useEffect, useState } from "react"

function LastSalesPage() {
  const [sales, setSales] = useState()
  const [isLoading, setIsLoading] = useState(false)

  useEffect(() => {
    setIsLoading(true)
    fetch(https://firebaseURL.json).then(response => response.json()).then(data => {
       const transformedSales = []

       for (const key in data) {
         transformedSales.push({id: key, username: data[key].username, volume: data[key].volume})
       }

       setSales()
       setIsLoading(false)
    })
  }, [])

  if (isLoading) { return <p>Loading...</p> }  

  return (
    <ul>
      {sales.map(sale => <li key={sale.id}>{sale.username} - ${sale.volume}</li>)}
    </ul>)
}

export default LastSalesPage

Note: The fetch API is available in the browser but also in “getStaticProps” and “getServerSideProps” which may be useful as well.

Also Note: firebase can translate URLs into database operations.

Also Note: The data we get back from firebase is not an array but an object of nested objects so you need to transform the data using a “for in” loop.

In the code above we will get an error that “Cannot read property ‘map’ of undefined,” which makes sense because initially “isLoading” is false and our sales state is undefined. In the useEffect we change the loading state but how react works is it only runs the useEffect after the entire component was evaluated and rendered for the first time. And so for the first render cycle sales is undefined. Solve this by adding the following code above the JSX return add:

if (!sales) { return <p>No Data Yet</p> }

This “no data yet” message will show up in the page source as the page is still pre-rendered by NextJS because that is the default unless you use “getStaticProps.”

Chapter 109 – Using the “useSWR” NextJS Hook

Nothing wrong with the code above, full control over entire component state and how data is being fetched. Such a common pattern you could put into your own custom hook or one that is created by a third party. Developed by NextJS and get some additional perks including, caching, automatically revalidation, retries on error, etc. Need to npm install “swr.”

import { useEffect, useState } from "react"
import useSWR from "swr"

function LastSalesPage() {
  const [sales, setSales] = useState()
  const { data, error } = useSWR('https://firebaseURL.json')

  useEffect(() => {
  if (data) {
  const transformedSales = []

  for (const key in data) {
         transformedSales.push({id: key, username: data[key].username, volume: data[key].volume})
       }
     setSales(transformedSales)
    }
  }, [data])

  if (error) { return <p>Failed to Load</p> } 
  if (!data || !sales) { return <p>Loading...</p> }
  
  return (
    <ul>
      {sales.map(sale => <li key={sale.id}>{sale.username} - ${sale.volume}</li>)}
    </ul>
  )

}

export default LastSalesPage

Note that now we now just use useEffect for simply transforming the data.

Chapter 110 – Combining Pre-Fetching With Client-Side Fetching

As a bonus, can be a pattern we use to render a basic snapshot and then still fetch the latest data from the client. Add “getStaticProps” although we could use “getServerSideProps.” Can’t use the “useSWR” hook inside of the “getStaticProps” so we will write ordinary fetch code instead.

When we are done running the fetch code to grab data from firebase URL and transforming data into transformedSales we take advantage of the promise returned.

export async function getStaticProps() {
  return fetch(https://firebaseURL.json)
    .then(response => response.json())
    .then(data => {
       const transformedSales = []

       for (const key in data) {
         transformedSales.push({
           id: key,
           username: data[key].username,
           volume: data[key].volume})
,
       }
}

return { props: {sales: transformedSales }, revalidate: 10 }

Note: could also use Async/Await with slightly different syntax.

And now what do we do with the props passed to the component function? Pass them as the initial state for the sales useState() hook. These will be used for initial sales and will then be overwritten. So now sales will not be undefined so change the similar code to:

if ( !data && !sales ) { return <p>Loading...</p>}

This combo can lead to the best user experience as you have some data from the start and then you update it on the fly from the browser.

Chapter 111 – Module Summary

NextJS pre-renders pages for us so we have some data from the start so this is data is available to search engine crawlers. You can use getStaticProps to pre-fetch data that you need before running the build process. You can also use “revalidate” key to keep the data fresh after build and deployment. If you have dynamic pages with dynamic parameters give NextJS the info it needs with “getStaticPaths.” May need to use “getServerSideProps” or use traditional client side data fetching including maybe using the optional useSWR hook. Can also use a combination of the above data-fetching strategies.

Chapter 112 – Module Resources