Codecademy Logo

Next.js Data Fetching

Data Fetching

In a Next.js app, data fetching can occur both on the server and the client. While client-side data fetching may be necessary in some cases, data fetching on the server should be prioritized.

Server-Side Data Fetching

In Next.js applications, data fetching should ideally occur on the server due to several benefits:

  • The server has direct access to the database, ensuring efficient data retrieval.
  • Fetching data on the server reduces client-server waterfalls, optimizing performance.
  • Server components enable data fetching and rendering in the same environment, enhancing efficiency.
  • Security is increased as sensitive data can remain unexposed to the client, reducing the risk of data breaches.

Route Handlers in Next.js

Route Handlers in Next.js are used to define custom request handlers responsible for fetching data and preparing responses for client interactions. These handlers are typically defined in the /app directory and are structured to handle various HTTP methods such as GET, POST, and PUT.

export async function GET() {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Failed to fetch data.');
}
const data = await response.json();
return data;
}

Fetch Calls in Server Components

In Next.js applications, fetch calls can be directly used within server components, enabling seamless data retrieval and processing on the server side. However, this practice can potentially lead to the exposure of sensitive data when not used in conjunction with the 'server-only' package.

// Example of fetch call within a Server Component
export async function MyServerComponent() {
const response = await fetch('https://api.example.com/data', { cache: 'no-store' });
if (!response.ok) {
throw new Error('Failed to fetch data.');
}
const data = await response.json();
// Additional logic for the component
}

'server-only' Package

The 'server-only' package serves to prevent server-only code from being transmitted to the client in Next.js applications.

Using the 'server-only' package can ensure that sensitive server-side logic or resources remain exclusive to the server environment, enhancing security and minimizing the risk of exposing critical information to clients.

// Example of using the 'server-only' package
import 'server-only';
// Server-only code here
export async function MyServerComponent() {
}

Cache Control in Data Fetching

Next.js automatically caches fetched data by default. To disable caching, include cache: 'no-store' in the options object of a fetch call.

Disabling caching ensures that data is fetched freshly from the server each time, preventing stale data issues in Next.js applications.

const response = await fetch('https://api.example.com/data', {
cache: 'no-store',
});

Sequential Data Fetching Pattern

When using the sequential data fetching pattern, requests create waterfalls as they happen one after the other. This approach is used when one data fetch depends on the result of another.

Parallel Data Fetching Pattern

In the parallel data fetching pattern, requests within a route occur simultaneously, without waiting for one request to complete before starting another. This approach makes it easier to fetch multiple resources concurrently, leading to faster performance and better user experience.

Optimizing Parallel Data Fetching

The parallel data fetching pattern can be optimized by preloading data. By creating a function, such as preload(), data can be fetched and cached before it needs to be rendered.

// utils/getPosts.tsx
import { cache } from 'react';
import 'server-only';
export const preload = () => {
void getPosts();
};
export const getPosts = cache(async () => {
const response = await fetch('https://api.com/some/route');
// more fetch logic
});
// In a component file
import { preload } from '../utils/getPosts';
import Posts from '../components/Posts/Posts';
export default function Home() {
preload();
return (
<Posts />
);
}

Cached Data Revalidation

Next.js enables cached data revalidation through two methods:

  • Time-based Revalidation: Set the interval for data revalidation.
  • On-demand Revalidation: Group data by path or tag, updating it collectively upon specific event triggers.

Time-based Revalidation

Time-based revalidation in Next.js allows for the automatic revalidation of cached data after a specified duration, measured in seconds. By setting the next.revalidate option in a fetch call, developers can control how frequently data is refreshed to ensure its accuracy and timeliness.

const response = await fetch('https://api.com/some/route', {
next: { revalidate: 30 } // Revalidate data every 30 seconds
});

Server Action in Next.js

A Server Action is an asynchronous function executed on the server. It is created using the 'use server' directive. This enables specific tasks to be executed on the server side, such as data revalidation or fetching.

'use server'
import { revalidateTag } from 'next/cache'
export async function updatePost() {
// Revalidate cached data with the 'posts' tag
revalidateTag('posts')
}
// The component that uses the updatePost() Server Action
import { updatePost } from './actions';
export default function EditPost() {
// Component logic here
return (
<form action={updatePost}>
<button type="submit">Update</button>
</form>
);
}

On-Demand Revalidation

On-demand revalidation ensures data freshness by updating cached data selectively when specific events occur. This optimization technique allows developers to control data revalidation either by path or tag, providing precise control over data fetching.

The revalidateTag() function from next/cache enables developers to trigger revalidation of cached data associated with a particular tag.

'use server'
import { revalidateTag } from 'next/cache';
// Server action to update cached data with a specific tag
export async function updateCachedData() {
revalidateTag('blogs'); // Revalidate cached data with the 'blogs' tag
}

Loading UIs

Loading UIs provides visual feedback to users, indicating that data is being fetched, as fetching large amounts of data can take time. These UIs, like spinners, skeletons, progress indicators, or loading messages, serve as instant loading states, replacing segments while data loads.

//loading.tsx
export default function Loading(){
return (
<p>Loading data...</p>
)
}

Streaming using <Suspense> boundaries

Streaming using <Suspense> boundaries allows a web app to progressively render and incrementally stream parts of the UI to the client, enhancing user experience by displaying content as it becomes available.

<Suspense fallback={<Loading />} >
<UserPosts />
</Suspense>

Server-Only Forms With Server Actions

Server-only forms can be created in a server component using Server Actions to efficiently manage form submissions by retrieving, validating, and processing user data on the server side.

// components/FeedbackForm/actions.ts
'use server'
import { redirect } from 'next/navigation'
export async function handleFeedback(formData: FormData) {
// process form data
redirect('/feedback/thankyou');
}
// components/FeedbackForm/FeedbackForm.tsx
import { handleFeedback } from './actions'
export default function FeedbackForm() {
return (
<form action={handleFeedback}>
{/* form fields */}
<button type="submit">Submit</button>
</form>
);
}

Learn more on Codecademy