Learn how to fetch data in Server and Client Components, stream loading states with Suspense, and share data across components with React.cache.
This page walks through how to fetch data in Server and Client Components, how to stream components that depend on slow data, and patterns for sequential and parallel data fetching.
Identical fetch requests in a React component tree are memoized by default, so you can fetch data in the component that needs it without worrying about duplicate network requests. Fetch requests are not cached by default and will block the page from rendering. Use the use cache directive to cache results, or wrap the fetching component in <Suspense> to stream fresh data at request time.
Since Server Components run on the server, credentials and query logic will never reach the client bundle. You can safely make database queries directly:
When data is slow, the whole route is blocked until all fetches complete. You can break the page into smaller chunks and progressively stream them to the client using React Suspense.There are two ways to add streaming:
Create a loading.js file in the same folder as your page to stream the entire page while data is being fetched:
export default function Loading() { return <div>Loading...</div>}
On navigation, the user immediately sees the layout and the loading state while the page renders. The new content is swapped in automatically once rendering completes.Behind the scenes, loading.js is nested inside layout.js and automatically wraps the page.js file in a <Suspense> boundary.
A layout that accesses uncached or runtime data (e.g. cookies(), headers(), or uncached fetches) does not fall back to a same-segment loading.js. Instead, it blocks navigation until the layout finishes rendering. Wrap uncached access in its own <Suspense> boundary, or move the data fetching into page.js where loading.js covers it.
<Suspense> gives you more granular control. You can immediately show page content that falls outside the boundary while streaming content inside it:
import { Suspense } from 'react'import BlogList from '@/components/BlogList'import BlogListSkeleton from '@/components/BlogListSkeleton'export default function BlogPage() { return ( <div> {/* Sent to the client immediately */} <header> <h1>Welcome to the Blog</h1> <p>Read the latest posts below.</p> </header> <main> {/* Streamed in when ready */} <Suspense fallback={<BlogListSkeleton />}> <BlogList /> </Suspense> </main> </div> )}
Design loading states to be meaningful. For example, use skeletons or spinners, or show a meaningful part of the future screen such as a cover photo and title. In development, you can preview and inspect loading states using the React DevTools.
Sequential fetching happens when one request depends on data from another. Use <Suspense> to stream in dependent data without blocking the entire page:
Client Components resolve the promise using use(), wrapped in <Suspense> for a fallback:
'use client'import { use, useContext } from 'react'import { UserContext } from '../user-provider'export function Profile() { const userPromise = useContext(UserContext) if (!userPromise) { throw new Error('Profile must be used within a UserProvider') } const user = use(userPromise) return <p>Welcome, {user.name}</p>}
Server Components can also call getUser() directly — because it’s wrapped with React.cache, multiple calls within the same request return the same memoized result:
import { getUser } from '../lib/user'export default async function DashboardPage() { const user = await getUser() // Cached — no duplicate fetch return <h1>Dashboard for {user.name}</h1>}
React.cache is scoped to the current request only. Each request gets its own memoization scope — results are never shared between requests.