Next.js Data Fetching
Fetch data in Server Components, run requests in parallel, and load data on the client when needed.
TL;DR
- Fetch data with
awaitinside anasyncServer Component. - Start independent requests together with
Promise.all()to save time. - Opt in to caching with
cache: 'force-cache'or'use cache'.
Fetching on the Server
fetch()Await a request inside an async Server Component and render the result.
const API = 'https://api.example.com';
export default async function Page() {
const res = await fetch(`${API}/posts`);
const posts = await res.json();
return <p>{posts.length} posts</p>;
}Database QueryQuery the database directly, with no API route in between.
import { sql } from '@/lib/db';
export default async function Page() {
const posts = await sql`SELECT * FROM posts`;
return <p>{posts.length} posts</p>;
}res.okCheck the response and throw, so the nearest error.tsx shows a fallback.
const res = await fetch(`${API}/posts`);
if (!res.ok) {
throw new Error('Failed to load posts');
}
const posts = await res.json();Avoiding Waterfalls
Promise.all()Run independent requests at the same time instead of one after another.
const [user, orders] = await Promise.all([
getUser(id),
getOrders(id),
]);PreloadStart a request early without awaiting it, then await it where it is needed.
const ordersPromise = getOrders(id); // starts now
const user = await getUser(id);
const orders = await ordersPromise;<Suspense>Let a slow component load on its own without blocking the rest of the page.
<h1>Dashboard</h1>
<Suspense fallback={<p>Loading stats...</p>}>
<SlowStats />
</Suspense>Caching Results
force-cacheCache one request so its result is reused across users and requests.
const res = await fetch(`${API}/posts`, {
cache: 'force-cache',
});next.revalidateRefresh a cached request in the background after a set number of seconds.
const res = await fetch(`${API}/posts`, {
next: { revalidate: 3600 },
});'use cache'Cache any async function, including database queries, when Cache Components is enabled.
export async function getPosts() {
'use cache';
return sql`SELECT * FROM posts`;
}Client and URL-Driven Data
use()Read a promise passed from a Server Component inside a Suspense boundary.
'use client';
import { use } from 'react';
export function Posts({
data,
}: {
data: Promise<Post[]>;
}) {
const posts = use(data);
return <p>{posts.length} posts</p>;
}useSWR()Fetch and refresh on the client for data that changes often.
'use client';
import useSWR from 'swr';
const fetcher = (url: string) =>
fetch(url).then((r) => r.json());
export function Profile() {
const { data } = useSWR('/api/user', fetcher);
return <p>{data?.name}</p>;
}searchParamsRefetch on the server whenever a filter in the URL changes.
export default async function Page({
searchParams,
}: {
searchParams: Promise<{ q?: string }>;
}) {
const { q = '' } = await searchParams;
const items = await search(q);
return <List items={items} />;
}Tips
- Fetch data in the component that needs it rather than passing it down, since identical
fetchcalls are deduplicated in one render. - Wrap slow sections in
<Suspense>, so the rest of the page appears while that data is still loading.
Warnings
- A
fetchcall is not cached by default in current Next.js, so addcache: 'force-cache'when the data rarely changes. - Awaiting requests one after another creates a waterfall, so use
Promise.all()when the requests do not depend on each other.
In Practice
Load a user and their orders together, and stream a slow stats panel separately.
- Promise.all starts both requests at once, so the wait is the slower of the two.
- The stats panel is its own async component, so it does not block the page.
- Suspense shows a fallback for the panel while the heading and orders render.
- Nothing here needs client JavaScript, because all the fetching is on the server.
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { getUser, getOrders, getStats } from '@/lib/data';
async function Stats() {
const stats = await getStats(); // slow
return <p>Revenue: {stats.revenue}</p>;
}
export default async function Page() {
const [user, orders] = await Promise.all([
getUser(),
getOrders(),
]);
return (
<main>
<h1>Welcome, {user.name}</h1>
<p>{orders.length} orders</p>
<Suspense fallback={<p>Loading stats...</p>}>
<Stats />
</Suspense>
</main>
);
}FAQ
Fetch in Server Components whenever you can. They run on the server, so they can call APIs and databases directly. Fetch on the client only for data that changes in response to the user.
No. Since Next.js 15, fetch requests are not cached unless you opt in. Add cache: 'force-cache', set next: { revalidate: 60 }, or use the 'use cache' directive.
Not in the App Router. getServerSideProps and getStaticProps belong to the Pages Router. In the App Router, you await data directly inside an async component.
Use a library like SWR or TanStack Query, or pass a promise from a Server Component and read it with use. Avoid fetching inside useEffect when a Server Component can do the job.