~4 min read • Updated Oct 25, 2025
Fetching Data in Server Components
You can fetch data in Server Components using the fetch API or a database/ORM. Just make your component async and await the data:
Example with fetch
// app/blog/page.tsx
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>
)
}Example with database
// app/blog/page.tsx
import { db, posts } from '@/lib/db'
export default async function Page() {
const allPosts = await db.select().from(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}Fetching Data in Client Components
In Client Components, you can use React’s use hook or libraries like SWR and React Query.
Streaming with use
Start by fetching data in a Server Component and pass the promise to a Client Component:
// app/blog/page.tsx
import Posts from '@/app/ui/posts'
import { Suspense } from 'react'
export default function Page() {
const posts = getPosts()
return (
<Suspense fallback={<div>Loading...</div>}>
<Posts posts={posts} />
</Suspense>
)
}Then use the use hook to resolve the promise:
// app/ui/posts.tsx
'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>
)
}Using SWR
// app/blog/page.tsx
'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>
)
}Request Deduplication and Caching
- GET/HEAD requests with the same URL are automatically deduplicated during a render pass.
- Use
cache: 'force-cache'in fetch options to enable Next.js Data Cache. - For database queries, wrap your function with React’s
cacheutility:
// app/lib/data.ts
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db'
export const getPost = cache(async (id: string) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(id)),
})
})Streaming in Next.js
With cacheComponents enabled, you can stream HTML chunks progressively to improve load time.
Using loading.tsx
Create a loading.tsx file in the route folder to show a loading UI while data is fetched:
// app/blog/loading.tsx
export default function Loading() {
return <div>Loading...</div>
}Using Suspense for granular streaming
// app/blog/page.tsx
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>
)
}Conclusion
Next.js provides flexible and powerful ways to fetch data in both Server and Client Components. With hooks, community libraries, smart caching, and streaming, you can build fast, dynamic, and maintainable interfaces.
Written & researched by Dr. Shahin Siami