Responses (0)
Disclaimer - Please read the first part of this blog post here before proceeding. It provides an overview of the Next.js application that's being built throughout this tutorial series, and it introduces developers to the Next.js framework. If you just want to jump straight into this tutorial, then clone the project repository and install the dependencies.
A major feature of the Next.js framework is the number of rendering strategies it supports:
Server-Side Rendering (SSR)
Static-Site Generation (SSG)
Incremental Static Regeneration (ISR)
Client-Side Rendering (CSR)
Each of these rendering strategies uses a different technique for data-fetching that's required for dynamically rendered content. For example, on the client-side, upon mounting a function component, you can call the useEffect
hook to perform side effects. such as data-fetching, and update the UI accordingly. Alternatively, you can use a React hook library called SWR that's built specifically for data-fetching. In Next.js, data-fetching for the other browserless rendering strategies involves exporting specific functions within a page component's file:
Rendering Strategy | Exported Function | Description |
Server-Side Rendering | getServerSideProps | On each incoming request, Next.js runs getServerSideProps . Any data fetched inside of getServerSideProps can be passed to the page component as props. Then, using the data from these props, Next.js pre-renders the page. |
Static-Site Generation | getStaticProps | At build time, Next.js runs getStaticProps . Any data fetched inside of getStaticProps can be passed to the page component as props. Then, using the data from these props, Next.js pre-renders the page. |
Static-Site Generation | getStaticPaths | Can only be exported for pages of dynamic routes. These pages must also export getStaticProps . Unlike getStaticPaths , this function determines the list of paths that must be statically generated by Next.js. The pages for these paths are pre-rendered. |
Incremental Static Regeneration | getStaticProps | At build time, Next.js runs getStaticProps , just like for static-site generation. However, adding the revalidate prop to getStaticProps tells Next.js to re-run getStaticProps every x seconds and update the contents of a static page with new props without having to rebuild the entire Next.js application. |
Below, I'm going to show you how to fetch data from an API with getStaticProps
.
Installation and Setup#
To get started, clone the project repository and install the dependencies.
If you're coming from first part of this tutorial series, then you can continue on from where the first part left off.
Obtaining an Access Token for the Petfinder API#
Currently, the home page lacks a grid of cards that allow users to navigate to different pet animal listings.

Therefore, we need to send a request to the Petfinder API to fetch a list of pet animal types. For the home page to be pre-rendered with the grid of cards, this data must be fetched within an exported getStaticProps
function in pages/index.tsx
. If this data is fetched from within a useEffect
hook, then the home page will only be able to render the grid of cards on the client-side, not at build time.
Let's export a getStaticProps
function and annotate it with the GetStaticProps
type, which will be imported from next
. Since getStaticProps
will fetch data, the function must be defined as an async
function. Since data that's returned from this function gets passed as props to the page component, we need this function to return the fetched pet animal types (types
). For now, we will return an empty array for types
.
(pages/index.tsx
)
import { NextPage, GetStaticProps } from "next";
import { AnimalType } from "../shared/interfaces/petfinder.interface";
import TypeCardsGrid from "../components/TypeCardsGrid";
export interface HomePageProps {
types: AnimalType[];
}
export const getStaticProps: GetStaticProps = async () => {
return {
props: {
types: []
}
};
};
const HomePage: NextPage<HomePageProps> = ({ types = [] }) => (
<section>
<h1 className="text-7xl font-extrabold tracking-tight text-transparent bg-clip-text bg-gradient-to-br from-purple-400 to-purple-800 sm:text-8xl lg:text-9xl">
Petfinder
</h1>
<p className="mt-7 mb-7 text-2xl text-gray-400">
Explore the Petfinder API and help pets find good homes.
</p>
{types.length > 0 && <TypeCardsGrid types={types} />}
</section>
);
export default HomePage;
To fetch any data from the Petfinder API, you must first authenticate your application and obtain an access token. Anytime data needs to be fetched from the Petfinder API, this access token must be added to the request's Authorization
header.
To obtain an access token, we send a POST
request to https://api.petfinder.com/v2/oauth2/token
. This request must be sent with the following payload:
{
"grant_type": "client_credentials",
"client_id": "<PETFINDER_CLIENT_ID>",
"client_secret": "<PETFINDER_CLIENT_SECRET>",
}
Your application's Petfinder client ID and secret should already be stored as environment variables (NEXT_PUBLIC_PETFINDER_CLIENT_ID
and NEXT_PUBLIC_PETFINDER_CLIENT_SECRET
respectively). If your application does not have a Petfinder client ID and secret, then visit the Petfinder developers portal and register for a developer account.
Upon sending a request to the POST /oauth2/token
endpoint, a successful response should contain the following:
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "..."
}
Note: expires_in
represents an expiration duration in seconds. This means that one hour after receiving the access token, the access token will expire, and you will need to obtain a new access token to continue fetching data from the Petfinder API. We will handle token expiration in a later part of this tutorial series.
Let's obtain an access token within the getStaticProps
function, like so:
(pages/index.tsx
)
import { NextPage, GetStaticProps } from "next";
// ...
const {
NEXT_PUBLIC_PETFINDER_API_URL,
NEXT_PUBLIC_PETFINDER_CLIENT_ID,
NEXT_PUBLIC_PETFINDER_CLIENT_SECRET,
} = process.env;
export const getStaticProps: GetStaticProps = async () => {
try {
const { access_token } = await (
await fetch(`${NEXT_PUBLIC_PETFINDER_API_URL}/oauth2/token`, {
headers: {
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
grant_type: "client_credentials",
client_id: NEXT_PUBLIC_PETFINDER_CLIENT_ID,
client_secret: NEXT_PUBLIC_PETFINDER_CLIENT_SECRET,
}),
})
).json();
} catch (err) {
console.error(err);
}
return {
props: {
types: []
}
};
};
// ...
Fetching a List of Pet Animal Types#
After obtaining an access token for the Petfinder API, let's send a GET
request to https://api.petfinder.com/v2/types
to fetch a list of pet animal types, like so:
// ...
export const getStaticProps: GetStaticProps = async () => {
let types: AnimalType[] = [];
try {
const { access_token } = await (
await fetch(`${NEXT_PUBLIC_PETFINDER_API_URL}/oauth2/token`, {
headers: {
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
grant_type: "client_credentials",
client_id: NEXT_PUBLIC_PETFINDER_CLIENT_ID,
client_secret: NEXT_PUBLIC_PETFINDER_CLIENT_SECRET,
}),
})
).json();
({ types } = await (
await fetch(`${NEXT_PUBLIC_PETFINDER_API_URL}/types`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${access_token}`,
},
})
).json());
} catch (err) {
console.error(err);
}
return {
props: {
types
}
};
};
// ...
Now let's verify that the home page displays a grid of cards. Spin up the application in development mode:
$ npm run dev
Within a browser, visit localhost:3000
.

If you hover over the Browse Listings
links, you will notice that they all point to the same route: /types
. To fix this problem, we must add an id
property to each pet animal type. The id
property should be a unique identifier that will be used as a...
Key for React to identify each item in the grid.
Route parameter for the "Browse Listings" URL (replaces
:type
in/types/:type
).
// ...
export const getStaticProps: GetStaticProps = async () => {
let types: AnimalType[] = [];
try {
const { access_token } = await (
await fetch(`${NEXT_PUBLIC_PETFINDER_API_URL}/oauth2/token`, {
headers: {
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
grant_type: "client_credentials",
client_id: NEXT_PUBLIC_PETFINDER_CLIENT_ID,
client_secret: NEXT_PUBLIC_PETFINDER_CLIENT_SECRET,
}),
})
).json();
({ types } = await (
await fetch(`${NEXT_PUBLIC_PETFINDER_API_URL}/types`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${access_token}`,
},
})
).json());
} catch (err) {
console.error(err);
}
return {
props: {
types:
types.length > 0
? types.map((type) => ({
...type,
id: (type._links.self.href.match(/\/types\/([\w-]+)$/) || "")[1],
}))
: types,
},
};
};
// ...
Reload the page. Now the links point to the correct URLs.
Let's verify that the grid is pre-rendered by Next.js at build time. Run the build
script to generate an optimized version of the application that's production-ready.
$ npm run build
If you check the contents of the generated index.html
file under the .next/server/pages
directory, then you will notice that the grid has been pre-rendered by Next.js.

You can also verify that Next.js has pre-rendered the grid by spinning up the application in production mode, which will serve the statically generated pages:
$ npm run start
If you open up the network tab of the developer tools and inspect the response for /
(index.html
), then you will notice that the page has already been pre-rendered prior to reaching the client's browser.

Another problem that you might have noticed is how slow the images load since they are high resolution images directly downloaded from Unsplash. We will address this in the next part of this tutorial series.
Next Steps#
If you find yourself stuck at any point during this tutorial, then feel free to check out the project's repository for this part of the tutorial here.
Proceed to the next part of this tutorial series to learn how to improve image loading with BlurHash.
If you want to learn more advanced techniques with TypeScript, React and Next.js, then check out our Fullstack React with TypeScript Masterclass:
