Web Development

Understanding Data Fetching in Next.js

April 11th, 2023 | By Jay Raj | 7 min read

Server-side rendering (SSR) is the technique of generating the page on the server side and then once it's ready, sending it over to the client side to hydrate. SSR is also good from a search engine optimization point of view since the page content is already available on the client side.

Depending on how you are planning to render your content, Next.js provides different ways to fetch the data. And in this tutorial, you'll learn how to fetch data in your Next.js application during SSR and client-side rendering.

Let's get started by creating your Next.js app.

Creating the Next.js App

To get started you'll be requiring Node.js >= 10.13. Type in the following command to check the Node version installed.

node -v


The above command should output the following:

C:\Users\Jay>node -v
v18.12.1


If your Node version is above 10.13 use the following command to create your Next.js app.


npx create-next-app next-fetch-data


It would prompt a couple of questions. Select the answers as shown:

  • Would you like to use TypeScript with this project? Yes

  • Would you like to use ESLint with this project? Yes

  • Would you like to use `src/` directory with this project? Yes

  • Would you like to use experimental `app/` directory with this project? No

  • What import alias would you like configured? Press Enter

Once done you will have your Next.js app created. Navigate to the project directory and start the app.

cd next-fetch-data
npm run dev


The above command will start the project in development mode and you will have the app running at http://localhost:3000.


next.j application running at http://localhost:3000

Understanding Data Fetching


Whenever a client sends a request to the server, it generates the HTML and sends it to the client side. The client-side then renders the HTML document in the browser.

Now the page rendered can be a complete HTML page document or the basic structure of a page document. If it's a complete HTML file, like in the case of static pages, then it will be rendered in the browser.

If it's a complete HTML page, it will be simply rendered. If it's a basic page structure, then there will be some script being run on the client side to generate the dynamic page content.

For example:

<html>
<head></head>
<body>
    <div id="content-container">
        <!-- 
                Dynamic content
will be here !!
             -->
    </div>
</body>
</html>


In the case of client-side rendering, the data will be fetched from the client once the app has loaded. For fetching the data during server-side rendering, Next.js provides a couple of methods based on the particular use case.

Let's dive a bit deeper into each use case to understand how we can fetch data.

getStaticProps

Let's say while loading your page, you are showing a list of countries in a drop-down. No matter to whom or from where the page is being requested, the country list remains the same.

In such cases, you can make use of `getStaticProps`. Now, what `getStaticProps` does is fetch the data from the API during build time and keep it aside as a JSON file. Whenever the page is requested, it is rendered with the data in the JSON file. So basically, `getStaticProps` is executed only once during build time, and the data is cached and rendered each time the page is requested. Now let's see it in action.

By default, we have an `index` page inside the `pages` folder. Remove the existing code and replace it with the following code:


export default function Home({users}:any) {
  return (
    // ## Iterating over the users list
    // ## and displaying the name
    <ul>
      {users.map((user:any) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}
export async function getStaticProps(context:any) {
  // ## making an API call
  const res = await fetch('/users')
  // ## parsing out the JSON data
  const users = await res.json()
  // ## returning the users as props to the page
  return {
    props: {
      users,
    },
  }
}


For Next.js to pre-render the page, you need to export the function `getStaticProps`. Inside the method, we are making an API call and pass the information as props to the page where it's rendered. Now let's try building the project:


npm run build


Inside the project directory, if you go to the page `.next/server/pages/` you will find a file called `index.json` which is the JSON data being passed as props to the page generated during build time. Now every time the page is requested, this file is reused instead of fetching data on every request.

Now, what if the data has changed since you last built your project?

One option would be to rebuild your project once again. But you can't be doing it every time the data changes. That takes us to Incremental Static Regeneration (ISR).

Incremental Static Regeneration


ISR is required when you need to update your static pages once you have built and deployed your project. To make use of ISR, you need to add a property called `revalidate` along with the `props` as shown:


export async function getStaticProps(context:any) {
  // ## making an API call
  const res = await fetch('/users')
  // ## parsing out the JSON data  
const users = await res.json()
  // ## returning the users as props to the page
  return {
    props: {
      users,
    },
    revalidate: 20
  }
}


So whenever you request a page for 20 seconds, you'll be shown the page with the cached data. Once you request the page after 20 seconds, it will trigger a revalidation, and the page will be regenerated with the new data.

Now, what do I do if I require fresh data on each request? You can use `getServerSideProps`.

getServerSideProps

Once you have exported a function called `getServerSideProps` in your Next.js page it will be executed on each request. The page will get pre-rendered on the server side with the data being passed from the `getServerSideProps` method. Here is an example of it in action,



export default function Home({users}:any) {  
return (
    // ## Iterating over the users list
    // ## and displaying the name
    <ul>
      {users.map((user:any) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}
export async function getServerSideProps(context:any) {
  // ## making an API call
  const res = await fetch('/users')
  // ## parsing out the JSON data
  const users = await res.json()
  // ## returning the users as props to the page
  return {
    props: {
      users
    }
  }
}



Unlike the `getStaticProps` the code inside `getServerSideProps` will get called each time the page is requested and pre-rendered on the server.

getStaticPaths

This method applies to dynamic routes, and if you are not familiar with dynamic routes, I would recommend reading about them in our previous blog article about understanding routing in Next.js.

Let's try to understand this with the help of an example. Let's assume we are showing a list of users on the page `/users`. Each user will have a detailed info page that is shown when clicking on `/users/1`, `/users/2` etc. where `1`,`2` indicate the respective user ids.

Using the `getStaticPaths` method, we'll create a list of dynamic paths, which will be pre-rendered to create the paths.

On the `index.tsx` page you'll be shown the list of users with a link to click to view the user details. We'll be using `getServerSideProps` method to fetch data in this case,



export default function Home({users}:any) {
  return (
    // ## Iterating over the users list
    // ## and displaying the name
    <ul>
      {users.map((user:any) => (
        <li key={user.id}>
          <a href={`/users/${user.id}`}>
          {user.name}
          </a>
        </li>
        
      ))}
    </ul>
  )
}
export async function getServerSideProps(context:any) {
  // ## making an API call
  const res = await fetch('/users')
  // ## parsing out the JSON data
  const users = await res.json()
  // ## returning the users as props to the page
  return {
    props: {
      users
    }
  }
}


Next, we'll create a dynamic page `/pages/users/[id].tsx` and add the following code:

export default function Home({userInfo}:any) {
    return (
      <table>
        <tbody>
        <tr>
            <td>
                Name
            </td>
            <td>
                {userInfo.name}
            </td>
        </tr>        <tr>
            <td>
                Email
            </td>
            <td>
                {userInfo.email}
            </td>
        </tr>
        <tr>
            <td>
                Phone Number
            </td>
            <td>
                {userInfo.phone}
            </td>
        </tr>
        </tbody>        
      </table>
    )
  }
  export async function getStaticProps({ params }:any) {

    // ## making an API call
    const res = await fetch(`/users/${params.id}`)

    // ## parsing out the JSON data
    const userInfo = await res.json()

    // ## returning the users as props to the page
    return {
      props: {
        userInfo
      }
    }
  }
  export async function getStaticPaths() {

    // ## making an API call
    const res = await fetch('/users')

    // ## parsing out the JSON data
    const users = await res.json()
    let params = users.map((user:any) => {
      return {
        params: {id : user.id.toString()}
      }
    })
    return {
      paths: params,
      fallback: false, // can also be true or 'blocking'
    }
  }



As seen in the above code, dynamic paths are created based on `params` generated inside the `getStaticPaths` method. And then during build time for each of the dynamic routes like `/users/1` the page is pre-rendered as in `getStaticProps`.

Save the above changes and build the project.

npm run build


And run the project based on the build,


npm start


Point your browser to http://localhost:3000 and you will get a list of users. On clicking each name, you'll be redirected to the specific dynamic route.


Client-side fetching


Last but not least is the client-side fetching. Here, data is fetched once the basic page structure has loaded. You can use `useEffect` hook to make client-side calls once the component has loaded, as shown:


useEffect(()=>{
const makeAPICall = async () =>{
const res = await fetch('/users');
const users = await res.json();
};
makeAPICall();
},[]);


Wrapping It Up


In this tutorial, we learned about different ways to fetch data during server-side rendering and client-side rendering. The method used differs based on your use case.

Jscrambler

The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.

View All Articles

Must read next

Application Security

How To Protect Next.js Apps with Jscrambler

In this article, we’ll look at how you can integrate Jscrambler into your Next.js app development workflow.

September 1, 2021 | By Jscrambler | 6 min read

Web Development

Understanding Routing in Next.js

In this tutorial, you'll learn the basics of how to route pages in your Next.js application.

February 28, 2023 | By Jay Raj | 9 min read

Section Divider

Subscribe to Our Newsletter