Choosing the right data fetching strategy in Next.js is crucial for delivering a performant and user-friendly experience. Next.js offers a variety of approaches, each with its own strengths and ideal use cases. Understanding these options will empower you to make informed decisions about how your application retrieves and displays data.
The primary consideration when selecting a data fetching strategy is when and where you want the data to be fetched. Do you need it at build time for static generation, on the server at request time, or on the client after the page has loaded?
Here's a breakdown of the most common data fetching strategies in Next.js, helping you navigate the choices:
- Static Site Generation (SSG): Pre-rendering at Build Time
This is ideal for content that doesn't change frequently, such as blog posts, marketing pages, or product listings. Next.js fetches the data at build time, generating static HTML files for each page. This results in incredibly fast load times because the server doesn't need to do any work when a user requests the page – it just serves the pre-built HTML.
You achieve SSG using getStaticProps in your page components. The data fetched here is available on the client-side during hydration. For dynamic routes, getStaticPaths is used to pre-render a list of paths.
export async function getStaticProps(context) {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return {
props: {
posts,
},
};
}- Server-Side Rendering (SSR): Rendering at Request Time
SSR is suitable for pages where the data needs to be fresh for every request, such as user dashboards, personalized content, or pages with frequently changing information. Next.js fetches the data on the server each time a user requests the page. This means the HTML is generated dynamically, ensuring the latest data is always displayed. However, it introduces a slight delay compared to SSG as the server has to process the request.
You implement SSR using getServerSideProps in your page components. This function runs on the server on every request.
export async function getServerSideProps(context) {
const res = await fetch('https://api.example.com/dashboard');
const dashboardData = await res.json();
return {
props: {
dashboardData,
},
};
}- Client-Side Rendering (CSR): Fetching Data in the Browser
This is the traditional way of fetching data in React applications. The initial HTML is rendered without the data, and then JavaScript fetches the data in the browser and updates the UI. This is useful for dynamic content that doesn't impact SEO or initial page load performance significantly, or for data that a user might interact with to trigger a fetch (e.g., searching, filtering).
You can achieve CSR using React's built-in hooks like useEffect and useState, or with data fetching libraries like SWR or React Query.
import { useEffect, useState } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/user-profile')
.then((res) => res.json())
.then(setData);
}, []);
if (!data) return <p>Loading...</p>;
return <div>{data.name}</div>;
}- Incremental Static Regeneration (ISR): The Best of Both Worlds
ISR combines the benefits of SSG with the ability to update static pages after the initial build. You can re-generate a page at regular intervals or on-demand without needing to rebuild your entire site. This is perfect for content that is mostly static but occasionally needs updating, like e-commerce product pages that might have stock level changes.
You enable ISR by adding a revalidate property to the return object of getStaticProps. This property specifies the time in seconds after which a page regeneration can occur.
export async function getStaticProps(context) {
const res = await fetch('https://api.example.com/products/1');
const product = await res.json();
return {
props: {
product,
},
revalidate: 60, // Regenerate this page at most once every 60 seconds
};
}- Streaming SSR with React Server Components (RSC)
Introduced in Next.js 13, React Server Components allow you to render components entirely on the server, sending only the necessary HTML and JavaScript to the client. This can significantly improve perceived performance by allowing parts of your page to stream in as they become ready, rather than waiting for the entire page to load. This is particularly powerful for complex applications with large amounts of dynamic data.
Server Components are the default in the App Router. They fetch data directly within the component, eliminating the need for getStaticProps or getServerSideProps for many use cases. You can also leverage client components alongside server components for interactivity.
// app/page.js (Server Component by default)
async function getData() {
const res = await fetch('https://api.example.com/featured-items');
return res.json();
}
export default async function Page() {
const featuredItems = await getData();
return (
<div>
<h1>Featured Items</h1>
<ul>
{featuredItems.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}Choosing the right strategy often involves a trade-off between performance, data freshness, and complexity. For optimal results, consider the characteristics of your data and the user's experience.
graph TD
A[Start: Need to fetch data] --> B{Data freshness requirement?};
B -- High (changes often) --> C[Server-Side Rendering (SSR)];
B -- Medium (updates periodically) --> D[Incremental Static Regeneration (ISR)];
B -- Low (static at build time) --> E[Static Site Generation (SSG)];
F[User Interaction/Dynamic UI] --> G[Client-Side Rendering (CSR)];
H[Modern App Router/RSC] --> I[Server Components (default for data fetching)];
I --> J{Is client interaction needed?};
J -- Yes --> K[Client Components];
J -- No --> I;
C --> L[Fetch on Server per Request];
D --> M[Fetch at Build, Revalidate Periodically];
E --> N[Fetch at Build, Serve Static HTML];
G --> O[Fetch in Browser After Initial Load];
I --> P[Fetch on Server, Stream Content];
L --> Q[Latest Data, Slower Initial Load];
M --> R[Good Balance of Speed and Freshness];
N --> S[Fastest Load Times];
O --> T[Fast Initial Load, Data Appears Later];
P --> U[Fast Initial Load, Content Appears as Ready];