How to use Next Js Fetch API
- Author: Md. Saad
- Published at: October 13, 2024
- Updated at: December 02, 2024
Introduction
In modern web development, Next.js has gained immense popularity for its features like server-side rendering (SSR), static site generation (SSG), and API routes. One of the most common tasks in any web app is fetching data from external sources. This blog will explore how to effectively use the Fetch API in Next.js for client-side and server-side data fetching.
Introduction to Fetch API
The Fetch API is a modern interface that allows you to make HTTP requests to servers, handling responses with promises. It is widely supported in most modern browsers and can be used for tasks like fetching data from an API or sending data to a server.
Basic Example of Fetch API
Next.js enhances the Web's fetch() API by enabling server-side requests to define custom caching strategies and revalidation behaviour for each request.
We can call fetch with async and await directly within Server Components.
export default async function Page() {
let data = await fetch('https://api.vercel.app/blog')
let posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Caching behaviour in Next.js Fetch API
Next.js enhances the Web's fetch() API by enabling server-side requests to define custom caching strategies and revalidation behaviour for each request.
We can call fetch with async and await directly within Server Components.
export default async function Page() {
let data = await fetch('https://api.vercel.app/blog')
let posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
In Next.js, the fetch() function has extended options that allow for fine control over caching behaviour. This is particularly useful in Next.js applications when dealing with server-side data fetching.
cache: 'force-cache' (default):
- Behavior: Next.js will first check its Data Cache to see if there’s a cached response for the request.
- If the cached response exists and is still fresh, it returns the cached data.
- If the cached data is stale or doesn't exist, it fetches the resource from the remote server and updates the cache with the fresh data.
cache: 'no-store':
- Behaviour: Next.js will always fetch the resource from the remote server, bypassing any cached versions.
- It will also not update the cache with the newly fetched data. This is useful when you need fresh data every time, for example, on every request in dynamic applications.
Example:
export default async function Page() {
let data = await fetch('https://api.vercel.app/blog', {cache: 'no-store'})
let posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Revalidation in Next.js Fetch API
In Next.js, the options.next.revalidate setting for the fetch() function allows you to define how long a resource should remain in the cache before being revalidated, which gives you more granular control over caching strategies.
revalidate: false:
Behaviour: This will cache the resource indefinitely (essentially forever).
- It is semantically the same as setting revalidate: Infinity, meaning the cached data won't expire automatically but may be removed by the browser or server due to cache eviction policies (e.g., memory constraints).
revalidate: 0:
- Behaviour: This prevents the resource from being cached at all. Every time this resource is requested, it will be fetched from the remote server.
- This is useful when you need completely fresh data every time a request is made, even if that means bypassing the cache entirely.
revalidate: number:
- Behaviour: This allows you to specify the cache lifetime of a resource in seconds.
- For example, if you set revalidate: 60, the resource will be cached for 60 seconds, after which it will be revalidated the next time it is requested.
- This is useful for data that doesn't change frequently but still needs periodic updates (like news articles, weather reports, etc.).
Example:
export default async function Page() {
let data = await fetch('https://api.vercel.app/blog', { next: { revalidate: false | 0 | number } })
let posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Note:
- No need to set cache when using revalidate: If you set revalidate to a specific number, it will manage the cache behaviour for you. Adding both cache and revalidate together can create conflicts.
- Conflicting options: If you set conflicting options such as { revalidate: 3600, cache: 'no-store' }, Next.js will throw an error. This is because cache: 'no-store' implies no caching at all, while revalidate: 3600 suggests caching for 3600 seconds. These two options directly contradict each other. To avoid conflicts and ensure proper caching behaviour, it's best to use only one option: either cache or revalidate—not both.
Revalidation in Next.js Fetch API
In Next.js, the options.next.revalidate setting for the fetch() function allows you to define how long a resource should remain in the cache before being revalidated, which gives you more granular control over caching strategies.
revalidate: false:
Behaviour: This will cache the resource indefinitely (essentially forever).
- It is semantically the same as setting revalidate: Infinity, meaning the cached data won't expire automatically but may be removed by the browser or server due to cache eviction policies (e.g., memory constraints).
revalidate: 0:
- Behaviour: This prevents the resource from being cached at all. Every time this resource is requested, it will be fetched from the remote server.
- This is useful when you need completely fresh data every time a request is made, even if that means bypassing the cache entirely.
revalidate: number:
- Behaviour: This allows you to specify the cache lifetime of a resource in seconds.
- For example, if you set revalidate: 60, the resource will be cached for 60 seconds, after which it will be revalidated the next time it is requested.
- This is useful for data that doesn't change frequently but still needs periodic updates (like news articles, weather reports, etc.).
Example:
export default async function Page() {
let data = await fetch('https://api.vercel.app/blog', { next: { revalidate: false | 0 | number } })
let posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Note:
- No need to set cache when using revalidate: If you set revalidate to a specific number, it will manage the cache behaviour for you. Adding both cache and revalidate together can create conflicts.
- Conflicting options: If you set conflicting options such as { revalidate: 3600, cache: 'no-store' }, Next.js will throw an error. This is because cache: 'no-store' implies no caching at all, while revalidate: 3600 suggests caching for 3600 seconds. These two options directly contradict each other. To avoid conflicts and ensure proper caching behaviour, it's best to use only one option: either cache or revalidate—not both.
Tags in Next.js Fetch API
In Next.js, the options.next.tags feature allows you to assign cache tags to a resource fetched via fetch(). These tags are useful for on-demand cache revalidation using the revalidateTag() function, providing finer control over cache invalidation across your app.
Key Details of tags:
- tags: ['collection']: This allows you to label specific data with one or more custom cache tags.
- On-demand revalidation: By tagging resources with specific cache tags, you can revalidate all resources with the same tag when needed using revalidateTag('collection').
- Max constraints:
- 1. Each tag can be up to 256 characters long.
- 2. You can assign a maximum of 64 tags per resource.
Example:
export default async function Page() {
let data = await fetch('https://api.vercel.app/blog', { next: { tags: ['collection'] } })
let posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Fetching data on the client
Client Components run in the browser, so they don't have direct access to server-side APIs and should be used for client-side dynamic data fetching. Next Js generally recommends to fetch data on the server side.
But, There are still cases where client-side data fetching is necessary. In such cases, instead of manually calling fetch inside a useEffect (which is generally not recommended), it's better to rely on popular React libraries like SWR or React Query to handle client-side fetching more efficiently. These libraries provide built-in caching, automatic revalidation, and error handling, making them a more robust solution for managing client-side data.
Example: In a file like app/page.js:
'use client'
import { useState, useEffect } from 'react'
export function Posts() {
const [posts, setPosts] = useState(null)
useEffect(() => {
async function fetchPosts() {
let res = await fetch('https://api.vercel.app/blog')
let data = await res.json()
setPosts(data)
}
fetchPosts()
}, [])
if (!posts) return <div>Loading...</div>
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
In this exampl:
- The 'use client' directive at the top makes this component a Client Component.
- useEffect is used to fetch data after the component mounts.
- The useState hook manages the data locally in the client-side state.
Reusing data across multiple functions
One key feature of Next.js is that fetch requests in generateMetadata and generateStaticParams are automatically memoized. Memoization ensures that the same fetch request isn't repeated unnecessarily. When fetching the same URL multiple times, the response is cached, improving performance by avoiding duplicate requests. Here is an example:
import { notFound } from 'next/navigation'
async function getPost(id) {
let res = await fetch(`https://api.vercel.app/blog/${id}`)
let post = await res.json()
if (!post) notFound()
return post
}
export async function generateStaticParams() {
let posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post) => ({
id: post.id,
}))
}
export async function generateMetadata({ params }) {
let post = await getPost(params.id)
return {
title: post.title,
}
}
export default async function Page({ params }) {
let post = await getPost(params.id)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
Conclusion
Finally, the Fetch API offers a great deal of flexibility for both client-side and server-side data fetching when used in a Next.js app with the App Router. While server-side fetching may be done directly within Server Components, ensuring optimal performance and SEO benefits, client-side fetching is handled by React hooks such as useEffect. Whether you're using internal data endpoints or external APIs, you can also organise your application using server actions and API routes to meet various demands. You may enhance the data-fetching process for a seamless and effective user experience in Next.js applications by utilising caching settings and revalidation procedures.
I hope you got a lot out of reading this. Please do not hesitate to contact StaticMania with any questions or feedback. Happy coding!
Implementing Lazy Loading for Components in Next.js
Lazy Loading in Next JS is used to improve the application's loading performance. It works by decreasing the amount of JavaScript required for a route
Read article