Fetch data in Server Components, run requests in parallel, and stream slow parts in with loading.tsx and Suspense.
Why: turn the component async and await the request. Identical fetch() calls in one render are de-duplicated automatically, so you can fetch in whichever component needs the data instead of passing it down through props.
// app/blog/page.tsx
export default async function Page() {
const res = await fetch('https://api.vercel.app/blog')
const posts = await res.json()
return (
<ul>
{posts.map((post: { id: string; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}Why: because Server Components run on the server, you can talk to your database or ORM right in the component — credentials and queries never reach the browser. Note: an ORM is a library that lets you query the DB with JavaScript instead of raw SQL.
// 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>
)
}Why: two awaits in a row run one-after-another (slow). If the requests don’t depend on each other, start them both first, then await together with Promise.all so they run at the same time.
// Sequential (slow): albums waits for artist to finish
const artist = await getArtist(id)
const albums = await getAlbums(id)
// Parallel (fast): both start immediately, then resolve together
const artistData = getArtist(id) // no await yet
const albumsData = getAlbums(id) // no await yet
const [artist2, albums2] = await Promise.all([artistData, albumsData])Why: add a loading.tsx beside your page and Next.js shows it instantly while the page renders, then swaps in the real content. Behind the scenes it wraps your page in a Suspense boundary for you. "Streaming" means sending UI in pieces as it’s ready.
// app/blog/loading.tsx — shown while app/blog/page.tsx loads
export default function Loading() {
return <p>Loading posts…</p>
}Why: when only part of a page is slow, wrap just that part in <Suspense>. The rest shows immediately and the slow component streams in when ready — no need to block the whole page.
import { Suspense } from 'react'
async function LatestPosts() {
const posts = await fetch('https://api.example.com/posts').then((r) => r.json())
return <ul>{posts.map((p: { id: string }) => <li key={p.id}>{p.id}</li>)}</ul>
}
export default function Page() {
return (
<>
<h1>My Blog</h1> {/* instant */}
<Suspense fallback={<p>Loading posts…</p>}>
<LatestPosts /> {/* streams in when ready */}
</Suspense>
</>
)
}