Data Fetching in Next.js

Data fetching is a core part of building any modern web application. In Next.js, one of the most powerful features is its flexible and efficient data-fetching system. Next.js allows developers to fetch data at build time, request time, or client-side, depending on the use case.

This post explores the three main data-fetching strategies in Next.js — getStaticProps, getServerSideProps, and client-side fetching. We’ll cover how they work, when to use each, and practical examples that show how to build fast and dynamic applications with Next.js.


1. Introduction to Data Fetching in Next.js

1.1 Why Data Fetching Matters

Every web application relies on data — whether it’s coming from a database, an API, or a local file. Data fetching in Next.js is unique because it supports both static generation and server-side rendering out of the box.

This means you can:

  • Pre-render pages at build time for better performance (Static Generation).
  • Fetch fresh data on every request for dynamic content (Server-Side Rendering).
  • Fetch data on the client side for interactive features.

1.2 The Three Main Approaches

Next.js provides three primary methods to fetch data:

  1. getStaticProps – Fetch data at build time (Static Generation).
  2. getServerSideProps – Fetch data on every request (Server-Side Rendering).
  3. Client-side fetching – Fetch data inside components after the page loads.

Each approach has trade-offs in terms of performance, scalability, and freshness of data.


2. Understanding Static Generation (getStaticProps)

2.1 What Is Static Generation?

Static Generation means Next.js pre-renders the page at build time.
The HTML is generated once and reused for every user, resulting in super-fast load times.

This is ideal for pages that don’t change frequently — such as blogs, product listings, or documentation.

2.2 Syntax and Usage

The getStaticProps function runs only on the server side during build time.

Example:

export async function getStaticProps() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  const posts = await res.json();

  return {
props: { posts }, // passed to the component as props
}; } export default function Blog({ posts }) { return (
<div>
  <h1>My Blog</h1>
  {posts.map(post => (
    <p key={post.id}>{post.title}</p>
  ))}
</div>
); }

In this example:

  • The API data is fetched once at build time.
  • The generated static HTML is served to all users.

2.3 Revalidating Data – Incremental Static Regeneration (ISR)

Static pages can become outdated if the data changes frequently.
Next.js solves this with Incremental Static Regeneration (ISR), which allows you to re-generate pages in the background after deployment.

Example with ISR:

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/products');
  const products = await res.json();

  return {
props: { products },
revalidate: 60, // re-generate page every 60 seconds
}; }

This way, the page stays fast like static content but remains fresh with updated data.

2.4 Using getStaticPaths for Dynamic Routes

When you have dynamic pages (like /posts/[id]), you must use getStaticPaths to define which paths to pre-render.

Example:

export async function getStaticPaths() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  const posts = await res.json();

  const paths = posts.map(post => ({
params: { id: post.id.toString() },
})); return { paths, fallback: false }; } export async function getStaticProps({ params }) { const res = await fetch(
https://jsonplaceholder.typicode.com/posts/${params.id}
); const post = await res.json(); return { props: { post } }; } export default function Post({ post }) { return (
<div>
  <h1>{post.title}</h1>
  <p>{post.body}</p>
</div>
); }

This approach generates each post page at build time, providing lightning-fast page loads.

2.5 When to Use getStaticProps

Use getStaticProps when:

  • Data is not changing frequently.
  • You need fast page loads and SEO-friendly pages.
  • Your data can be fetched ahead of time (e.g., blogs, docs, portfolios).

3. Understanding Server-Side Rendering (getServerSideProps)

3.1 What Is Server-Side Rendering?

Server-Side Rendering (SSR) means Next.js pre-renders the page on every request.
Unlike Static Generation, the page is generated dynamically at runtime — perfect for pages where data changes often.

3.2 Syntax and Usage

getServerSideProps runs on the server at request time, not during build.

Example:

export async function getServerSideProps() {
  const res = await fetch('https://jsonplaceholder.typicode.com/users');
  const users = await res.json();

  return { props: { users } };
}

export default function Users({ users }) {
  return (
<div>
  <h1>Users List</h1>
  {users.map(user => (
    <p key={user.id}>{user.name}</p>
  ))}
</div>
); }

Here, every time a user visits /users, the data is fetched fresh from the API.

3.3 Accessing Request Context

You can access the context object inside getServerSideProps, which includes:

  • req (request object)
  • res (response object)
  • params, query, and cookies

Example:

export async function getServerSideProps(context) {
  const { query } = context;
  const search = query.search || '';
  const res = await fetch(https://api.example.com/search?q=${search});
  const results = await res.json();

  return { props: { results } };
}

This allows you to build dynamic, query-based pages that respond instantly to user input.

3.4 When to Use getServerSideProps

Use getServerSideProps when:

  • Data changes frequently or must be fetched on every request.
  • You need user-specific data (e.g., dashboards or authenticated content).
  • You require access to request context (cookies, headers, etc.).

Avoid SSR for static or rarely changing data — it’s slower than static generation because it runs on every request.


4. Client-Side Data Fetching

4.1 What Is Client-Side Fetching?

Client-side data fetching happens after the page loads in the browser.
Instead of pre-rendering data, the component fetches it dynamically using libraries like fetch, axios, or React Query.

This is ideal for data that:

  • Doesn’t need to be SEO-indexed.
  • Changes frequently or in real time.
  • Depends on user actions (like search results or filters).

4.2 Example Using useEffect

import { useEffect, useState } from 'react';

export default function Posts() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
  .then(res => res.json())
  .then(data => setPosts(data));
}, []); return (
<div>
  <h1>Client-Side Posts</h1>
  {posts.map(post => (
    <p key={post.id}>{post.title}</p>
  ))}
</div>
); }

Here, data fetching happens inside the browser, not on the server.

4.3 Example with SWR

Next.js recommends using the SWR (Stale-While-Revalidate) library for client-side fetching.

Install SWR:

npm install swr

Example:

import useSWR from 'swr';

const fetcher = url => fetch(url).then(res => res.json());

export default function Products() {
  const { data, error } = useSWR('https://api.example.com/products', fetcher);

  if (error) return <div>Error loading...</div>;
  if (!data) return <div>Loading...</div>;

  return (
&lt;div&gt;
  &lt;h1&gt;Products&lt;/h1&gt;
  {data.map(item =&gt; (
    &lt;p key={item.id}&gt;{item.name}&lt;/p&gt;
  ))}
&lt;/div&gt;
); }

SWR automatically caches data, revalidates in the background, and keeps your UI responsive.

4.4 When to Use Client-Side Fetching

Use client-side fetching when:

  • You don’t need SEO (e.g., dashboards, chat apps).
  • Data is user-specific or real-time.
  • You want fast UI updates without full page reloads.

5. Combining Data Fetching Strategies

In many real-world applications, you’ll combine multiple fetching strategies.

Example:

  • Use getStaticProps for static content (product descriptions).
  • Use client-side fetching for dynamic content (user reviews).

Example:

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/products');
  const products = await res.json();

  return {
props: { products },
revalidate: 120,
}; } export default function ProductPage({ products }) { const [reviews, setReviews] = React.useState([]); React.useEffect(() => {
fetch('https://api.example.com/reviews')
  .then(res =&gt; res.json())
  .then(data =&gt; setReviews(data));
}, []); return (
&lt;div&gt;
  &lt;h1&gt;Products&lt;/h1&gt;
  {products.map(p =&gt; (
    &lt;div key={p.id}&gt;
      &lt;h2&gt;{p.name}&lt;/h2&gt;
      &lt;p&gt;{p.description}&lt;/p&gt;
    &lt;/div&gt;
  ))}
  &lt;h2&gt;Latest Reviews&lt;/h2&gt;
  {reviews.map(r =&gt; (
    &lt;p key={r.id}&gt;{r.comment}&lt;/p&gt;
  ))}
&lt;/div&gt;
); }

This hybrid approach gives you both speed and freshness.


6. Comparing Data Fetching Methods

MethodRunsData FreshnessSEOPerformanceUse Case
getStaticPropsBuild timeStale until revalidateYesExcellentBlogs, marketing pages
getServerSidePropsRequest timeAlways freshYesModerateDashboards, user data
Client-side FetchingBrowserDynamicNoDependsSearch, filters, updates

7. Handling Errors in Data Fetching

7.1 Using Try-Catch

Always handle potential errors in data fetching functions.

export async function getServerSideProps() {
  try {
const res = await fetch('https://api.example.com/users');
const users = await res.json();
return { props: { users } };
} catch (error) {
return { notFound: true };
} }

7.2 Graceful Client-Side Error Handling

function App() {
  const [data, setData] = React.useState(null);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
fetch('https://api.example.com/data')
  .then(res =&gt; res.json())
  .then(setData)
  .catch(setError);
}, []); if (error) return <p>Error loading data</p>; if (!data) return <p>Loading...</p>; return <div>{data.title}</div>; }

8. SEO Considerations

  • Static Generation (getStaticProps) is best for SEO since the content is pre-rendered.
  • Server-Side Rendering (getServerSideProps) also provides full SEO benefits, but with higher response time.
  • Client-side Fetching is not ideal for SEO because search engines may not wait for JavaScript execution.

For SEO-sensitive pages like blogs or product pages, always prefer static or server-side rendering.


9. Caching and Performance Optimization

9.1 API Caching

Use caching headers or middleware to reduce server load.

Example (in API routes):

export default async function handler(req, res) {
  const data = await fetch('https://api.example.com/info').then(r => r.json());
  res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate');
  res.status(200).json(data);
}

9.2 Image Optimization

Use Next.js <Image> for optimized image delivery with caching and lazy loading.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *