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.
In Next.js applications, data fetching should ideally occur on the server due to several benefits:
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;}
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 Componentexport 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'
PackageThe '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' packageimport 'server-only';// Server-only code hereexport async function MyServerComponent() {}
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',});
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.
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.
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.tsximport { 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 fileimport { preload } from '../utils/getPosts';import Posts from '../components/Posts/Posts';export default function Home() {preload();return (<Posts />);}
Next.js enables cached data revalidation through two methods:
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});
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' tagrevalidateTag('posts')}// The component that uses the updatePost() Server Actionimport { updatePost } from './actions';export default function EditPost() {// Component logic herereturn (<form action={updatePost}><button type="submit">Update</button></form>);}
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 tagexport async function updateCachedData() {revalidateTag('blogs'); // Revalidate cached data with the 'blogs' tag}
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.tsxexport default function Loading(){return (<p>Loading data...</p>)}
<Suspense>
boundariesStreaming 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 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 dataredirect('/feedback/thankyou');}// components/FeedbackForm/FeedbackForm.tsximport { handleFeedback } from './actions'export default function FeedbackForm() {return (<form action={handleFeedback}>{/* form fields */}<button type="submit">Submit</button></form>);}