icon

How to fetch Data in Next JS new App router

summary

Learn how to properly fetch data using Next.js app router and explore the differences from the old page router. Also, understand advanced data fetching techniques with bearer tokens.

tech stack
Next.js
App Router
Data Fetching
published date
last edited time
thumbnail

Data fetching is one of the biggest changes in the Next.js app router

When I switched from the Page router to the App router, I got stuck on data fetching for days. There were no tutorials available on how to use the App router properly.

So, I read the Next.js App router docs, made many projects, and learned a lot.

At first, I wondered why Next.js made such a big change because the App router is completely different from the Page router.

But after learning the App router properly, I loved the change because it made many things simple and straightforward.

Let's compare data fetching in the Page router vs App router.

Here, I'm using the JSONPlaceholder API, which doesn't need any authentication. Feel free to try out the code and see the results for yourself.

json

Copy

{
  "id": 1,
  "name": "Leanne Graham",
  "username": "Bret",
  "email": "Sincere@april.biz",
  "address": {
    "street": "Kulas Light",
    "suite": "Apt. 556",
    "city": "Gwenborough",
    "zipcode": "92998-3874",
    "geo": {
      "lat": "-37.3159",
      "lng": "81.1496"
    }
  },
  "phone": "1-770-736-8031 x56442",
  "website": "hildegard.org",
  "company": {
    "name": "Romaguera-Crona",
    "catchPhrase": "Multi-layered client-server neural-net",
    "bs": "harness real-time e-markets"
  }
}
data we're getting from the API.

Page Router

In the Page Router, we used to fetch data using getStaticProps like this:

typescript

Copy

import { Inter } from "next/font/google";

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

export default function Home({ user }: any) {
  return (
    <main
      className={`flex justify-center items-center h-screen ${inter.className}`}
    >
      <article className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
        <p className="text-lg font-bold mb-4">User Information</p>
        <div className="mb-4">
          <p className="text-sm">ID: {user.id}</p>
          <p className="text-sm">Username: {user.username}</p>
        </div>
        <section className="mb-4">
          <p className="text-sm font-bold mb-2">Address:</p>
          <p className="text-sm">Street: {user.address.street}</p>
          <p className="text-sm">Suite: {user.address.suite}</p>
          <p className="text-sm">City: {user.address.city}</p>
          <p className="text-sm">Zipcode: {user.address.zipcode}</p>
          <div className="mt-2">
            <p className="text-sm font-bold">Geo:</p>
            <p className="text-sm">Lat: {user.address.geo.lat}</p>
            <p className="text-sm">Lng: {user.address.geo.lng}</p>
          </div>
        </section>
        <div className="mb-4">
          <p className="text-sm">Phone: {user.phone}</p>
          <p className="text-sm">Website: {user.website}</p>
        </div>
        <section>
          <p className="text-sm font-bold mb-2">Company:</p>
          <p className="text-sm">Name: {user.company.name}</p>
          <p className="text-sm">Catch Phrase: {user.company.catchPhrase}</p>
          <p className="text-sm">BS: {user.company.bs}</p>
        </section>
      </article>
    </main>
  );
}

export async function getStaticProps() {
  const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const user = await res.json();

  return {
    props: {
      user,
    },
  };
}
pages/index.ts

In the "pages" folder, there's an "index.js" file representing the homepage.

We fetch data using getStaticProps, returning it as props. Then, we pass these props to the Home function, where we use the data.

App router

In the new App Router, we no longer use getStaticProps. Instead, we directly fetch the data as shown here.

typescript

Copy

export default async function Home() {
  const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const data = await res.json();
  return (
    <main className="flex justify-center items-center h-screen">
      <article className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
        <p className="text-lg font-bold mb-4">User Information</p>
        <div className="mb-4">
          <p className="text-sm">ID: {data.id}</p>
          <p className="text-sm">Username: {data.username}</p>
        </div>
        <section className="mb-4">
          <p className="text-sm font-bold mb-2">Address:</p>
          <p className="text-sm">Street: {data.address.street}</p>
          <p className="text-sm">Suite: {data.address.suite}</p>
          <p className="text-sm">City: {data.address.city}</p>
          <p className="text-sm">Zipcode: {data.address.zipcode}</p>
          <div className="mt-2">
            <p className="text-sm font-bold">Geo:</p>
            <p className="text-sm">Lat: {data.address.geo.lat}</p>
            <p className="text-sm">Lng: {data.address.geo.lng}</p>
          </div>
        </section>
        <div className="mb-4">
          <p className="text-sm">Phone: {data.phone}</p>
          <p className="text-sm">Website: {data.website}</p>
        </div>
        <section>
          <p className="text-sm font-bold mb-2">Company:</p>
          <p className="text-sm">Name: {data.company.name}</p>
          <p className="text-sm">Catch Phrase: {data.company.catchPhrase}</p>
          <p className="text-sm">BS: {data.company.bs}</p>
        </section>
      </article>
    </main>
  );
}
app/page.tsx

You can also see how simple and straightforward it looks. It's very clean. We're not passing props anymore.

Note: As you can see, we used async and await here. This indicates that this is an asynchronous process. It should first wait for the data to be fetched before proceeding further.

You need to use async and await every time you fetch data on any page.

Output from the code
Output from the code

Data Fetching with Bearer Token

In real projects, APIs often require authentication for access. This typically involves including a bearer token in the request headers. Let's see how to fetch data using a bearer token in the App router.

We make a "utils" folder in the main directory or "src". Inside this "utils" folder, we create a file called "api.js". You can choose any name for this file.

javascript

Copy

export const STRAPI_API_TOKEN = process.env.STRAPI_API_TOKEN;

export const API_URL = process.env.API_URL;

export const fetchDataFromApi = async (endpoints) => {
  const options = {
    method: "GET",
    headers: {
      Authorization: "Bearer " + STRAPI_API_TOKEN,
    },
  };

  const res = await fetch(`${API_URL}${endpoints}`, options);
  const data = await res.json();

  return data;
};
utils/api.js

We create this separate function so that we can fetch data using a bearer token. This way, we can use the function in any file without repeatedly passing the bearer token every time we fetch data.

Page router

javascript

Copy

import ProductCard from "@/components/ProductCard";
import Wrapper from "@/components/Wrapper";
import { fetchDataFromApi } from "@/utils/api";

export default function Home({ products }) {
  return (
    <main>
      <Wrapper>
        {/* products grid start */}
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 my-14 px-5 md:px-0">
          {products?.data?.map((product) => (
            <ProductCard key={product?.id} data={product} />
          ))}
        </div>
        {/* products grid end */}
      </Wrapper>
    </main>
  );
}

export async function getStaticProps() {
  const products = await fetchDataFromApi("/api/products?populate=*");
  return {
    props: { products: products },
  };
}
pages/index.js

App router

javascript

Copy

import ProductCard from "@/components/ProductCard";
import Wrapper from "@/components/Wrapper";
import { fetchDataFromApi } from "@/utils/api";

export default async function Home() {
  const products = await fetchDataFromApi("/api/products?populate=*");

  return (
    <main>
app/page.js more simple

If you only want to use the fetched data on this page, then use this.

Or

javascript

Copy

import ProductCard from "@/components/ProductCard";
import Wrapper from "@/components/Wrapper";
import { fetchDataFromApi } from "@/utils/api";

export async function getData() {
  const products = await fetchDataFromApi("/api/products?populate=*");

  return products;
}

export default async function Home() {
  const products = await getData();

  return (
    <main>
      <Wrapper>
        {/* products grid start */}
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 my-14 px-5 md:px-0">
          {products?.data?.map((product) => (
            <ProductCard key={product?.id} data={product} />
          ))}
        </div>
        {/* products grid end */}
      </Wrapper>
    </main>
  );
}
app/page.js

You can also create a separate data fetching function outside the page function in the App router.

I've named this function getData, but you can choose any name you prefer like getBlogs, getProducts, etc.

By exporting this function, you can call it from any page and retrieve the data.

Note: As you can see, we did not use fetch here; instead, we are using the fetchDataFromApi function we made earlier and passing the API endpoint.

🤍

This is it for this blog. I will soon add a blog about dynamic data fetching.