Fetching data in Server Components with fetch API
To fetch data with the fetch API in a Server Component, turn your component into an asynchronous function and await the fetch call. Identical fetch requests in a React component tree are memoized by default. Fetch requests are not cached by default and will block the page from rendering until the request is complete. Use the 'use cache' directive to cache results, or wrap the fetching component in <Suspense> to stream fresh data at request time.
Fetching data with ORM or database in Server Components
Server Components are rendered on the server, so credentials and query logic will not be included in the client bundle. This allows you to safely make database queries using an ORM or database client. Requests should be properly authenticated and authorized, following the data security guide.
Three recommended data fetching approaches in Next.js
The three recommended approaches for fetching data in Next.js are: (1) the fetch API in Server Components, (2) an ORM or database client in Server Components, and (3) React's use API or community libraries like SWR or React Query in Client Components.
Streaming in Next.js
Streaming allows you to break a page into smaller chunks and progressively send those chunks from the server to the client. This improves initial load time and user experience. There are two ways to use streaming: wrapping a page with a loading.js file, or wrapping a component with <Suspense>.
Using Suspense for streaming specific components
<Suspense> allows you to be more granular about what parts of the page to stream. Content outside the <Suspense> boundary is sent to the client immediately, while dynamic content inside the boundary is streamed in. This is recommended over loading.js when using <Suspense> closer to the runtime or uncached data access.
React use API for streaming data from server to client
You can use React's use API to stream data from the server to client. Start by fetching data in your Server component without awaiting it and pass the promise to your Client Component as a prop. In the Client Component, use the use API to read the promise. The <Suspense> boundary allows the fallback to be shown while the promise is being resolved.
Community libraries for client-side data fetching
You can use community libraries like SWR or React Query to fetch data in Client Components. These libraries have their own semantics for caching, streaming, and other features.
Sequential data fetching pattern
Sequential data fetching happens when one request depends on data from another. For example, a component may need data from a first request to fetch data in a subsequent request. This pattern blocks everything until the first request completes. Consider caching the result if the data changes infrequently.
Parallel data fetching pattern
Parallel data fetching happens when data requests in a route are eagerly initiated and start at the same time. Layouts and pages are rendered in parallel by default, so each segment starts fetching data as soon as possible. To fetch multiple requests in parallel within a component, call fetch without awaiting, then await them with Promise.all(). Requests begin as soon as fetch is called.
React.cache for reusing data across components
You can wrap a data-fetching function in React.cache so multiple components in the same request share one result instead of refetching. React.cache is scoped to the current request only; each request gets its own memoization scope with no sharing between requests.
Parallel data fetching with Promise.all example
To fetch multiple requests in parallel, initiate requests by calling fetch functions without awaiting them, then await them together with Promise.all(). For example: const artistData = getArtist(username); const albumsData = getAlbums(username); const [artist, albums] = await Promise.all([artistData, albumsData]);
Promise.allSettled for error handling in parallel requests
If one request fails when using Promise.all, the entire operation will fail. To handle this, you can use the Promise.allSettled method instead to allow other requests to complete even if one fails.
Bots and crawlers streaming behavior
Bots and crawlers are served differently from browsers. Next.js waits for data fetching to finish and sends the fully rendered page instead of streaming it progressively.
Creating meaningful loading states
An instant loading state is fallback UI shown immediately to the user after navigation. For the best user experience, design loading states that are meaningful and help users understand the app is responding, such as skeletons, spinners, or a small but meaningful part of future screens like a cover photo or title.
Server Component data fetching using fetch API example
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
This example shows how to turn a component into an asynchronous function, await a fetch call, and render the results.
React.cache data fetching function example
import { cache } from 'react'
export const getUser = cache(async () => {
const res = await fetch('https://api.example.com/user')
return res.json()
})
This wraps a data-fetching function so multiple components in the same request share one result instead of refetching.
Sequential data fetching example
export default async function Page({ params }) {
const { username } = await params
const artist = await getArtist(username)
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<Playlists artistID={artist.id} />
</Suspense>
</>
)
}
async function Playlists({ artistID }) {
const playlists = await getArtistPlaylists(artistID)
return (
<ul>
{playlists.map((playlist) => (
<li key={playlist.id}>{playlist.name}</li>
))}
</ul>
)
}
This example shows sequential data fetching where Playlists can only fetch data after getArtist() resolves.
React use API with Suspense example
'use client'
import { use } from 'react'
export default function Posts({ posts }) {
const allPosts = use(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
This Client Component example shows how to use the use API to read a promise passed from a Server Component.
SWR library for client-side data fetching example
'use client'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((r) => r.json())
export default function BlogPage() {
const { data, error, isLoading } = useSWR(
'https://api.vercel.app/blog',
fetcher
)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
This example shows using SWR for client-side data fetching in a Client Component.
Suspense with streaming data example
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
export default function BlogPage() {
return (
<div>
<header>
<h1>Welcome to the Blog</h1>
<p>Read the latest posts below.</p>
</header>
<main>
<Suspense fallback={<BlogListSkeleton />}>
<BlogList />
</Suspense>
</main>
</div>
)
}
This example shows how to use <Suspense> to stream specific components while showing a fallback skeleton.
When to use inline loading states
Use inline loading states when each component should render its own loading UI. With inline loading states, each component independently manages its loading presentation.
Pass Promise to Client Component and unwrap with use()
When a Client Component only needs to read server data once, pass it a Promise and unwrap it with React's use() function. This avoids adding a data-fetching library for data that never revalidates on the client.
Client data-fetching libraries for shared browser cache
Use a client data-fetching library such as SWR, TanStack Query, or Apollo Client when Client Components need a shared browser cache. These libraries can add focus revalidation, interval polling, request deduplication, or optimistic updates across components.
Three common client data-fetching patterns
Client data-fetching libraries support three common patterns: (1) Inline loading states using useSWR or useQuery, where data becomes available after browser request post-hydration; (2) Suspense loading states using useSWR with suspense: true or useSuspenseQuery, where data becomes available after browser request post-hydration; (3) Provided by the server using SWRConfig fallback or HydrationBoundary, where data becomes available at initial render or streamed from server.
When to use Suspense loading states
Use Suspense to define loading UI at a boundary and coordinate which parts of the interface reveal together or progressively.
Browser-driven interactions like autocomplete
For browser-driven interactions such as autocomplete, you can use either client-only pattern (inline or Suspense). The initial result waits for hydration and a browser request, which is often the right tradeoff for data that is not needed until an interaction.
Provide initial data from Server Component
Provide initial data from a Server Component when the server knows what the initial render needs. The value can be included in the initial render or streamed through Suspense. The library receives it in the React Server Component payload and can continue managing it in the browser.
Roles of Server Components, libraries, and mutations in data flow
Server Components provide the initial data, scoped to the segment that owns it. The data-fetching library stores the browser value under a shared cache identity. Mutations can update the browser cache immediately and invalidate cached server data so the next render can read a fresh value.
Restore browser value if optimistic update fails
An optimistic update should restore the previous browser value if the write fails. If the server read is not cached, there is no server tag to invalidate.
updateTag() invalidation method
Use updateTag(tag) when a Server Action must make its update visible immediately. The next server read waits for fresh data.
revalidateTag(tag, 'max') invalidation method
Use revalidateTag(tag, 'max') when the update is passive or stale data is acceptable. The next server read serves stale data while revalidating.
revalidateTag(tag, { expire: 0 }) invalidation method
Use revalidateTag(tag, { expire: 0 }) when a webhook or external system requires immediate expiration. The next server read waits for fresh data.
useSWR hook for client-side data fetching
Use useSWR when the component should render its own loading and error states. The hook returns an object with data, error, and isLoading properties. A conditional key (passing null as the key) delays the request until the interaction provides an input.
SWR suspense option for Suspense boundary integration
Use suspense: true option when the nearest Suspense boundary should define the loading UI. With suspense: true, Suspense handles the initial no-data state. The data property is defined after Suspense resolves.
SWR isLoading vs isValidating distinction
The isLoading value is true when a request is running and there is no loaded data to display. The isValidating value is true whenever a request is running, including background revalidation.
SWR suspense revalidation behavior
With suspense: true, later revalidation for the same key keeps the current data rendered instead of showing the Suspense fallback again. Use isValidating to provide background refresh feedback.
SWR revalidateIfStale option behavior
Setting revalidateIfStale: false skips revalidation when the hook mounts with cached data. This setting applies to every mount, unlike TanStack Query's staleTime. Focus, reconnect, polling, and mutate can still revalidate the key regardless of this setting.
SWR fallback data from Server Component
With SWR 2.3.0 and React 19, a Server Component can provide fallback data before the client takes over using SWRConfig with the fallback property. The fallback and Client Component must use the same SWR key.
SWR freshness and caching with fallback data
SWR does not provide a time-based freshness window for fallback data. By default, SWR treats fallback data as stale and starts a browser revalidation after hydration. To refresh on a schedule, set refreshInterval.
SWR key and fallback must match exactly
The fallback key and the useSWR key must match exactly. If they drift, SWR ignores the fallback value and fetches on the client.
SWR parallel vs sequential Suspense reads
Independent Suspense reads can start in parallel when they render in sibling components. Multiple Suspense reads in one component run sequentially.
SWR owns separate browser cache independent of cacheLife
SWR owns a separate browser cache, so its revalidation options do not need to match cacheLife settings on the server data.
Coordinating server and client caches with shared contract
Define both SWR key and server cache tag identities in one shared cache contract. Keep this contract free of server-only and client-only imports so both cache layers can reuse it.
SWR mutate with optimistic updates
Use useSWRConfig hook to access mutate function. Pass the write to mutate with optimisticData option. SWR shows the optimistic value immediately and rolls it back if the write fails. Options include revalidate: false, rollbackOnError: true, and throwOnError: false.
updateTag function for server cache invalidation
Call updateTag in a Server Action when it changes cached server data that must reflect a write immediately. updateTag expires tagged server data so the next cached read returns fresh data.
SWR key points to Route Handler with GET method
The SWR key should point to a Route Handler with a GET method. The Route Handler can call the same server function that provides the fallback, while the browser uses the URL for revalidation and polling.
SWRConfig scoping for server-provided data pattern
Scope SWRConfig to the route segment that owns the data. The provider keeps the fallback close to its consumer and avoids adding feature data to a shared layout.
SWR fetcher function signature
The fetcher function receives a URL string parameter and should return a Promise resolving to the data. It should throw an Error on non-ok responses.
SWR conditional key for delayed requests
Pass null as the SWR key to delay the request until the interaction provides an input. When the key becomes truthy (e.g., a query string), the request begins.
SWR suspense with error boundaries
When using suspense: true, handle request errors with the nearest error boundary rather than checking an error property.
Server Action invalidates cached server data with updateTag
After a Server Action writes to the database, call updateTag with the cache tag to expire the tagged server data. This ensures the next cached server read returns fresh data. The tag should match the tag used in cacheTag on the data fetching function.
useQuery for client-side data fetching with loading states
Use useQuery when the component should render its own loading and error states. The useQuery hook returns an object with data (defaults to undefined), error, and isPending properties. The enabled option can delay the request until an interaction provides input, such as enabled: query.length > 0.
useSuspenseQuery for client data with Suspense boundaries
Use useSuspenseQuery when the nearest Suspense boundary should define the loading UI. The hook does not return isPending or error properties; instead it throws errors to the nearest error boundary and suspends until data is available. After initial data loads, later refetches keep the cached data rendered instead of showing the Suspense fallback again.
Multiple useSuspenseQuery calls run sequentially
Multiple useSuspenseQuery calls in one component run sequentially, creating request waterfalls. Put independent queries in sibling components or use useSuspenseQueries to fetch in parallel.
TanStack Query dehydration for server-provided initial data
TanStack Query 5.40.0 or later can dehydrate pending queries. Call prefetchQuery without awaiting it on the server to avoid blocking rendering, then pass the dehydrated state to HydrationBoundary. Use dehydrate with shouldDehydrateQuery callback that returns true for pending status to include in-flight queries.
Override queryFn on server for relative URL resolution
When providing initial data from a Server Component, override the queryFn in the prefetchQuery call because the Route Handler's relative URL only resolves in the browser. Define the actual fetch-based queryFn in the client component instead.
staleTime duration for hydrated data freshness
The staleTime option prevents an immediate client refetch by keeping the hydrated data fresh for the specified duration (e.g., 30_000 milliseconds). Choose a duration based on how quickly the data can change in your application.
Optimistic updates with useMutation onMutate and onError
Use useMutation's onMutate callback to update the cache immediately before the server write. Store the previous value and restore it in onError if the write fails. The onMutate async function should cancel in-flight queries, get the previous data, and set optimistic data.
Call updateTag when Server Action changes cached read
Call updateTag when a Server Action changes a cached read that must reflect the write immediately. An uncached read does not have a server tag to update and does not benefit from updateTag.
Server Functions dispatched and awaited sequentially
Server Functions are designed for server-side mutations. The client currently dispatches and awaits them one at a time. This is an implementation detail and may change.