You can fetch data in Server Components using the fetch API or a database/ORM. Just make your component async and await the data:
// 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>
)
}// 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>
)
}In Client Components, you can use React’s use hook or libraries like SWR and React Query.
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>
)
}// 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>
)
}cache: 'force-cache' in fetch options to enable Next.js Data Cache.cache utility:// 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)),
})
})With cacheComponents enabled, you can stream HTML chunks progressively to improve load time.
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>
}// 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>
)
}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.