Master NextJS 13 Data Fetching with this Step-by-Step Guide

Master NextJS 13 Data Fetching with this Step-by-Step Guide

The release of NextJS 13 brought about a plethora of new and impressive features, with one standout being the updated data fetching and management process. The fetch API replaced the more complicated functions, including getServerSideProps, getStaticProps, and getInitialProps.

Video

If you prefer watching a video, you can check out the Youtube video posted on the same

fetch() API

React recently introduces support for async/await in Server Components. You can now write Server Components using standard JavaScript await syntax by defining your component as an async function and that is what turned out to be a big plus point for NextJS 13.

Server Components

This is all you need to do to fetch data in NextJS now:

import { Post } from "@/lib/types";
import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"] });

const getPosts = async (): Promise<Post[]> => {
  const data = await fetch("https://jsonplaceholder.typicode.com/posts");
  const posts = await data.json();

  return posts;
};

export default async function Posts() {
  const posts = await getPosts();
  console.log(posts);

  return (
    <div className={inter.className}>
      <h1>Posts</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

The data is no more serialised so you can pass any type of data, including Dates, Maps, Sets, etc.

Also, note the fact that you no longer need to do this on the page level, like if you did before using getStaticProps or getServerSideProps. You can do this inside any component

Client Components

For now, if you need to fetch data in a Client Component, NextJS 13 recommends using a third-party library such as SWR or React Query.

React also introduced the use hook that accepts a promise conceptually similar to await. use handles the promise returned by a function in a way that is compatible with components, hooks, and Suspense.

"use client";

import { Post } from "@/lib/types";
import { Inter } from "next/font/google";
import { use } from "react";

const inter = Inter({ subsets: ["latin"] });

const getPosts = async (): Promise<Post[]> => {
  const data = await fetch("https://jsonplaceholder.typicode.com/posts");
  const posts = await data.json();

  return posts;
};

export default function ClientPosts() {
  const posts = use(getPosts());

  return (
    <div className={inter.className}>
      <h1>Posts</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

Static Data Fetching

fetch by default caches the data. So, even if the data from the API changes, when you refresh the page, the site doesn't update the data. This works great for sites which have static data which seldom changes. The example above demonstrates how to do this because it is the same as doing

fetch("https://jsonplaceholder.typicode.com/posts", {
  cache: "force-cache",
});

Dynamic Data Fetching

You can tell the fetch API to never cache the data by changing force-cache to no-cache or no-store(both signify the same in NextJS).

fetch("https://jsonplaceholder.typicode.com/posts", {
  cache: "no-cache"
});

Dynamic Params Data Fetching

Say, you link all the posts to another page /posts/[postId] and fetch the data here once you're in there. You'd do something like this:

// app/posts/[postId]/page.tsx

import { Post } from "@/lib/types";
import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"] });

// params: {
//   postId: aksdjlkjasd
// }

const getPost = async (id: string): Promise<Post> => {
  const data = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
  const post = await data.json();

  return post;
};

export default async function PostPage({
  params: { postId },
}: {
  params: {
    postId: string;
  };
}) {
  const post = await getPost(postId);
  return (
    <div className={inter.className}>
      <h1>{post.title}</h1>
      <p>Post ID: {postId}</p>
      <p>{post.body}</p>
    </div>
  );
}

Now, this works great, but when you try to build it, look at the response:

It says that /posts/[postId] is server-side rendered at runtime even though /posts is static. This is because NextJS doesn't know what all routes(postIds) exist. So, to enhance this further, you can add this function to the /posts/[postId]/page.tsx page

export const generateStaticParams = async () => {
  const data = await fetch("https://jsonplaceholder.typicode.com/posts");
  const posts = await data.json();

  return posts.map((post: Post) => ({
    params: {
      postId: post.id.toString(),
    },
  }));
};

This now tells NextJS, that all these postIds exist and we want it to statically generate them at build time if possible. Now, if we try rebuilding the app, this happens

Now, see that /posts/[postId] is static HTML + JSON! And, that would further optimise your app really well.

Revalidating Data

Say, your app is not fully dynamic but still sometimes the data does change, you can add this to your /posts/[postId]/page.tsx page

export const revalidate = 3600; // in seconds

This will tell NextJS to revalidate the data every hour. And the interesting this is, you can do this on the page level as well as the layout level.

You can also do this per fetch by tweaking the fetch call as follows

fetch("https://jsonplaceholder.typicode.com/posts", {
  next: {
    revalidate: 3600,
  },
});

And that is it!

Conclusion

It takes a while to wrap your head around all this, but this is what NextJS does so well and one of the biggest plus points about it, and once you get a hold of it, it's really powerful.

You can find all the code over here

Do give the video a look if you like that more, and stay tuned for more such posts!