Home/Blog/Understanding data fetching in Next.js
Home/Blog/Understanding data fetching in Next.js

Understanding data fetching in Next.js

Farrukh Rasool
Nov 15, 2024
7 min read

Key Takeaways:

  • Learn about server-side and client-side fetching, and when to use each for optimal performance.

  • Explore how Next.js caches server-side data automatically, reducing server load and improving response times.

  • Compare sequential and parallel data fetching strategies to minimize load times and improve user experience.

  • Discover how Incremental Static Regeneration (ISR) allows static pages to stay up-to-date as content changes.

Next.js is a React framework that allows developers to easily create high-performance, scalable, and SEO-friendly web applications. One of the key reasons developers choose Next.js is because it offers various data fetching strategies on both the server and client sides that allow developers to optimize for SEO, load times, and dynamic content updates. Whether you’re building a blog, e-commerce site, or e-learning platform, mastering these techniques is key to building scalable, high-performance applications. In this blog, we’ll break down these strategies and explore when and how to use each effectively.

Next.js
Next.js

Suppose you’re building an online learning platform like Educative, where users expect to see the latest course content, real-time progress updates, and personalized course recommendations—all without delay. To achieve this, you need a robust data-fetching strategy that not only delivers fresh content but also ensures optimal performance. In traditional React applications, data fetching is often managed on the client side, which can lead to slower page loads and poor SEO. However, with Next.js, you have the flexibility to fetch data both server-side and client-side, giving you the power to build high-performance, interactive applications.

Data fetching is one of Next.js’s core features. It allows developers to fetch data on the server and the client. Whether you’re pulling data from APIs, databases, or third-party services, Next.js provides robust tools to manage performance and optimize your app for users and search engines.

Server-side data fetching with fetch#

Next.js makes server-side data fetching straightforward. By default, it occurs in Server Components. This means data is fetched during the initial server render, ensuring fast load times and optimized SEO performance.

export default async function ProductsPage() {
const response = await fetch('https://educative.io/api/products');
const products = await response.json();
return (
<ul>
{products.map((product) => (
<li key={product.id}>
<h2>{product.name}</h2>
<p>{product.description}</p>
</li>
))}
</ul>
);
}
Data fetching with Fetch API

In this example, we use the Fetch API to retrieve a list of blog posts, which are rendered on the server. The response from fetch will be automatically cached. When no dynamic behavior is present, the page can be prerendered at build time.

By default, Next.js caches the response from fetch, which boosts performance by serving cached data on repeated requests. However, if you want to disable caching (e.g., for real-time updates), you can customize the fetch options as follows:

const data = await fetch('https://educative.io/api/products', { cache: 'no-store' });

With cache: 'no-store', the response is always fresh and never cached.

Want to build something interactive? Try this project: Build an Interactive E-Library Using Next.js and Tailwind.

Server-side data fetching from databases or ORMs#

Next.js also integrates with databases efficiently. Whether you’re using an ORM or querying a database directly, server-side data fetching and caching are handled similarly to API requests.

import { db, blogs } from '@/lib/db';
export default async function BlogPage() {
let blogsData = await db.select().from(blogs);
return (
<ul>
{blogsData.map((blog) => (
<li key={blog.id}>{blog.title}</li>
))}
</ul>
)
}
Data fetching from databases or ORMs

Note: For more control, use the dynamic = 'force-dynamic' directive to ensure that data is fetched dynamically every time.

Client-side data fetching#

Though server-side fetching is preferred over this approach for SEO and performance purposes, client-side data fetching can still be useful for dynamic or interactive components, such as when data changes based on user interactions, like filtering products, searching, or pagination.

'use client';
import { useState, useEffect } from 'react';
export default function SearchProducts() {
const [query, setQuery] = useState('');
const [products, setProducts] = useState([]);
useEffect(() => {
if (!query) return;
async function fetchProducts() {
const response = await fetch(`https://educative.io/api/products?q=${query}`);
const data = await response.json();
setProducts(data);
}
fetchProducts();
}, [query]);
return (
<div>
<input
type="text"
placeholder="Search for products..."
onChange={(e) => setQuery(e.target.value)}
/>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
Client-side data fetching

In the above example, users can search for products, and the data is fetched dynamically on the client side based on their input.

Sequential and parallel data fetching#

When building large-scale applications, understanding the distinction between parallel and sequential data fetching can significantly impact performance.

Sequential and parallel data fetching
Sequential and parallel data fetching

Sequential#

The requests depend on each other, so they are made one after the other. While this can be necessary in cases where data from one request is required to make the next request, it often leads to longer loading times.

export default async function PropertyPage({ params: { id } }) {
// Step 1: Fetch property details based on the ID
const propertyResponse = await fetch(`https://educative.io/api/properties/${id}`);
const property = await propertyResponse.json();
// Step 2: Fetch agent details using the agent ID from the property data
const agentResponse = await fetch(`https://educative.io/api/agents/${property.agentId}`);
const agent = await agentResponse.json();
return (
<div>
<h1>{property.name}</h1>
<p>Location: {property.location}</p>
<h2>Contact Agent</h2>
<p>{agent.name}</p>
<p>{agent.phone}</p>
</div>
);
}
Sequential data fetching

In the example above, we first fetch the property details based on the propertyId. Once the property details are fetched, we extract the agentId and use it to fetch the agent’s details.

Parallel#

Multiple requests are initiated simultaneously. This reduces the overall load time since the requests run concurrently. You can achieve this by starting the requests outside the component rendering and using Promise.all to wait for all of them to complete.

async function getProduct(id) {
return fetch(`https://educative.io/api/products/${id}`).then(res => res.json());
}
async function getReviews(id) {
return fetch(`https://educative.io/api/products/${id}/reviews`).then(res => res.json());
}
export default async function ProductPage({ params: { id } }) {
// Initiate both requests in parallel
const [product, reviews] = await Promise.all([getProduct(id), getReviews(id)]);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<h2>Customer Reviews</h2>
<ul>
{reviews.map((review) => (
<li key={review.id}>
<strong>{review.author}:</strong> {review.comment}
</li>
))}
</ul>
</div>
);
}
Parallel data fetching

In the example above, both the product details and the reviews are fetched at the same time using Promise.all(). This reduces the time to render the page because both requests are executed in parallel.

To get a hands on experince on Next.js rendering techniques, refer to the Build a Music Sharing App with Next.js and the MERN Stack project.

Key differences between sequential and parallel data fetching:

Feature

Sequential Data Fetching

Parallel Data Fetching

Timing

Requests made one after the other

Requests made simultaneously

Performance

Slower as it waits for each request

Faster due to concurrent execution

SEO Impact

When a request depends on data from another

When multiple independent requests are needed

Example

Fetching related data like property and agent details

Fetching independent data like products and reviews

Incremental Static Regeneration (ISR)#

In many traditional static sites, the content is generated only once at the build time. This approach is good for performance but isn’t ideal for applications that need to reflect frequent updates, like blogs, product catalogs, or news sites. Incremental Static Regeneration (ISR) in Next.js offers the best of both worlds—the speed and performance of static generation combined with the flexibility of dynamic updates.

Incremental Static Regeneration
Incremental Static Regeneration

With ISR, you can generate static pages at build time and update them incrementally as data changes without rebuilding the entire site. It allows you to statically generate a page once during the build and then regenerate it at a specified interval or on-demand. This ensures that pages are served as fast static files but are also updated regularly with fresh content.

  • Revalidate time: You specify how often the page should be updated by setting a revalidate interval in seconds. Often referred to as time-based revalidation.

  • On-demand regeneration: You can manually trigger page regeneration when data changes, offering full control over content updates.

Time-based revalidation#

Let’s look at an example of time-based revalidation. Suppose you’re building a blog where new posts are added daily, and you want to ensure that the readers always get the latest content. With ISR, you can regenerate the blog post pages every few minutes to reflect the newest posts.

export const revalidate = 5; // Revalidate every 5 seconds
export default async function BlogPostPage({ params }) {
const response = await fetch(`https://api.myblog.com/posts/${params.id}`);
const post = await response.json();
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
Time-based revalidation

The page is regenerated every 5 seconds. If a user visits within the 5-second window, they see the cached version. After that, the page is automatically regenerated in the background for the next visitor. This approach ensures your readers get fast-loading pages without sacrificing fresh content.

On-demand regeneration#

Sometimes, you may want to revalidate a page immediately after a significant change, such as when a new product is added, or a blog post is updated. Next.js allows you to trigger revalidation programmatically using revalidatePath.

The example below triggers the revalidation of the /products page as soon as a new product is added or updated, ensuring that the latest content is always available.

import { revalidatePath } from 'next/cache';
export async function revalidateProducts() {
// Trigger revalidation for the product listing page
revalidatePath('/products');
}
Using the revalidatePath function

Key benefits of ISR#

  • By regenerating pages on demand, you avoid the overhead of rebuilding the entire app, leading to faster build times.

  • Since ISR serves static pages, it’s ideal for improving SEO, as search engines can crawl static content.

  • Users get the benefits of static pages (quick load times), while the content is always kept up-to-date.

  • ISR scales well for applications with large datasets or frequently changing content, like blogs, e-commerce sites, or news websites.

Continue learning Next.js#

By mastering these data-fetching techniques in Next.js, you can build dynamic, SEO-friendly applications that scale effortlessly. By utilizing techniques like server-side fetching, client-side fetching, and ISR, you can optimize your app to deliver the right data at the right time. Whether you’re developing a static blog or a real-time dashboard, Next.js equips you with the tools you need for success.

If you're looking to deepen your understanding of data fetching in real-world scenarios, try the following:

  1. Next.js Internationalization: Building a Multilingual Blog App: This project provides insight into managing content across multiple languages and also helps you practice crucial data-fetching techniques—like dynamically loading localized content based on user preferences—making it an excellent way to solidify your Next.js data-fetching skills.

  2. Building Dynamic Web Applications with Next.js: This course focuses on mastering data-fetching techniques with Next.js, both on the server and client sides. By the end, you’ll be equipped to build production-ready applications that prepare you to handle dynamic content efficiently and optimize load times in real-world scenarios.

Frequently Asked Questions

What is the difference between server-side and client-side data fetching in Next.js?

Server-side data fetching occurs during the initial server render, providing fast load times and improved SEO as the content is pre-rendered.

Client-side data fetching, on the other hand, happens in the browser after the page has loaded, making it ideal for dynamic or interactive components where data changes based on user actions.

How does caching work with server-side data fetching in Next.js?

When should I use sequential vs. parallel data fetching?

Can I use both server-side and client-side data fetching in the same application?


  

Free Resources